diff --git "a/345円212円250円346円200円201円350円247円204円345円210円222円347円263円273円345円210円227円/347円212円266円346円200円201円345円216円213円347円274円251円346円212円200円345円267円247円.md" "b/345円212円250円346円200円201円350円247円204円345円210円222円347円263円273円345円210円227円/347円212円266円346円200円201円345円216円213円347円274円251円346円212円200円345円267円247円.md" index 9e41c8de5c..e7f63adbe7 100644 --- "a/345円212円250円346円200円201円350円247円204円345円210円222円347円263円273円345円210円227円/347円212円266円346円200円201円345円216円213円347円274円251円346円212円200円345円267円247円.md" +++ "b/345円212円250円346円200円201円350円247円204円345円210円222円347円263円273円345円210円227円/347円212円266円346円200円201円345円216円213円347円274円251円346円212円200円345円267円247円.md" @@ -31,22 +31,24 @@ tags: ['动态规划', '核心框架'] 什么叫「和 `dp[i][j]` 相邻的状态」呢,比如前文 [最长回文子序列](https://labuladong.github.io/article/fname.html?fname=子序列问题模板) 中,最终的代码如下: -```cpp -int longestPalindromeSubseq(string s) { - int n = s.size(); - // nxn 的 dp 数组全部初始化为 0 - vector> dp(n, vector(n, 0)); +```java +int longestPalindromeSubseq(String s) { + int n = s.length(); + // dp 数组全部初始化为 0 + int[][] dp = new int[n][n]; // base case - for (int i = 0; i < n; i++) + for (int i = 0; i < n; i++) { dp[i][i] = 1; + } // 反着遍历保证正确的状态转移 - for (int i = n - 2; i>= 0; i--) { + for (int i = n - 1; i>= 0; i--) { for (int j = i + 1; j < n; j++) { // 状态转移方程 - if (s[i] == s[j]) + if (s.charAt(i) == s.charAt(j)) { dp[i][j] = dp[i + 1][j - 1] + 2; - else - dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]); + } else { + dp[i][j] = Math.max(dp[i + 1][j], dp[i][j - 1]); + } } } // 整个 s 的最长回文子串长度 @@ -93,7 +95,7 @@ for (int i = n - 2; i>= 0; i--) { if (s[i] == s[j]) dp[j] = dp[j - 1] + 2; else - dp[j] = max(dp[j], dp[j - 1]); + dp[j] = Math.max(dp[j], dp[j - 1]); } } ``` @@ -112,7 +114,7 @@ for (int i = n - 2; i>= 0; i--) { 那么问题已经解决了一大半了,只剩下二维 `dp` 数组中的 `dp[i+1][j-1]` 这个状态我们不能直接从一维 `dp` 数组中得到: -```cpp +```java for (int i = n - 2; i>= 0; i--) { for (int j = i + 1; j < n; j++) { if (s[i] == s[j]) @@ -120,7 +122,7 @@ for (int i = n - 2; i>= 0; i--) { dp[j] = ?? + 2; else // dp[i][j] = max(dp[i+1][j], dp[i][j-1]); - dp[j] = max(dp[j], dp[j - 1]); + dp[j] = Math.max(dp[j], dp[j - 1]); } } ``` @@ -131,7 +133,7 @@ for (int i = n - 2; i>= 0; i--) { **那么如果我们想得到 `dp[i+1][j-1]`,就必须在它被覆盖之前用一个临时变量 `temp` 把它存起来,并把这个变量的值保留到计算 `dp[i][j]` 的时候**。为了达到这个目的,结合上图,我们可以这样写代码: -```cpp +```java for (int i = n - 2; i>= 0; i--) { // 存储 dp[i+1][j-1] 的变量 int pre = 0; @@ -141,7 +143,7 @@ for (int i = n - 2; i>= 0; i--) { // dp[i][j] = dp[i+1][j-1] + 2; dp[j] = pre + 2; else - dp[j] = max(dp[j], dp[j - 1]); + dp[j] = Math.max(dp[j], dp[j - 1]); // 到下一轮循环,pre 就是 dp[i+1][j-1] 了 pre = temp; } @@ -173,12 +175,13 @@ for (int i = 5; i--) { 那么现在我们成功对状态转移方程进行了降维打击,算是最硬的的骨头啃掉了,但注意到我们还有 base case 要处理呀: -```cpp +```java // dp 数组全部初始化为 0 -vector> dp(n, vector(n, 0)); +int[][] dp = new int[n][n]; // base case -for (int i = 0; i < n; i++) +for (int i = 0; i < n; i++) { dp[i][i] = 1; +} ``` 如何把 base case 也打成一维呢?很简单,记住空间压缩就是投影,我们把 base case 投影到一维看看: @@ -188,29 +191,31 @@ for (int i = 0; i < n; i++) 二维 `dp` 数组中的 base case 全都落入了一维 `dp` 数组,不存在冲突和覆盖,所以说我们直接这样写代码就行了: -```cpp -// 一维 dp 数组全部初始化为 1 -vector dp(n, 1); +```java +// base case:一维 dp 数组全部初始化为 1 +int[] dp = new int[n]; +Arrays.fill(dp, 1); ``` 至此,我们把 base case 和状态转移方程都进行了降维,实际上已经写出完整代码了: -```cpp -int longestPalindromeSubseq(string s) { - int n = s.size(); - // base case:一维 dp 数组全部初始化为 0 - vector dp(n, 1); +```java +int longestPalindromeSubseq(String s) { + int n = s.length(); + // base case:一维 dp 数组全部初始化为 1 + int[] dp = new int[n]; + Arrays.fill(dp, 1); for (int i = n - 2; i>= 0; i--) { int pre = 0; for (int j = i + 1; j < n; j++) { int temp = dp[j]; // 状态转移方程 - if (s[i] == s[j]) + if (s.charAt(i) == s.charAt(j)) dp[j] = pre + 2; else - dp[j] = max(dp[j], dp[j - 1]); + dp[j] = Math.max(dp[j], dp[j - 1]); pre = temp; } } diff --git "a/345円212円250円346円200円201円350円247円204円345円210円222円347円263円273円345円210円227円/351円253円230円346円245円274円346円211円224円351円270円241円350円233円213円351円227円256円351円242円230円.md" "b/345円212円250円346円200円201円350円247円204円345円210円222円347円263円273円345円210円227円/351円253円230円346円245円274円346円211円224円351円270円241円350円233円213円351円227円256円351円242円230円.md" index aaa96659de..0109e370c9 100644 --- "a/345円212円250円346円200円201円350円247円204円345円210円222円347円263円273円345円210円227円/351円253円230円346円245円274円346円211円224円351円270円241円350円233円213円351円227円256円351円242円230円.md" +++ "b/345円212円250円346円200円201円350円247円204円345円210円222円347円263円273円345円210円227円/351円253円230円346円245円274円346円211円224円351円270円241円350円233円213円351円227円256円351円242円230円.md" @@ -34,7 +34,7 @@ tags: ['动态规划'] 这是力扣第 887 题「鸡蛋掉落」,我描述一下题目: -你面前有一栋从 1 到 `N` 共 `N` 层的楼,然后给你 `K` 个鸡蛋(`K` 至少为 1)。现在确定这栋楼存在楼层 `0 <= F <= N`,在这层楼将鸡蛋扔下去,鸡蛋**恰好没摔碎**(高于 `F` 的楼层都会碎,低于 `F` 的楼层都不会碎)。现在问你,**最坏**情况下,你**至少**要扔几次鸡蛋,才能**确定**这个楼层 `F` 呢? +你面前有一栋从 1 到 `N` 共 `N` 层的楼,然后给你 `K` 个鸡蛋(`K` 至少为 1)。现在确定这栋楼存在楼层 `0 <= F <= N`,在这层楼将鸡蛋扔下去,鸡蛋**恰好没摔碎**(高于 `F` 的楼层都会碎,低于 `F` 的楼层都不会碎,如果鸡蛋没有碎,可以捡回来继续扔)。现在问你,**最坏**情况下,你**至少**要扔几次鸡蛋,才能**确定**这个楼层 `F` 呢? 也就是让你找摔不碎鸡蛋的最高楼层 `F`,但什么叫「最坏情况」下「至少」要扔几次呢?我们分别举个例子就明白了。 diff --git "a/346円225円260円346円215円256円347円273円223円346円236円204円347円263円273円345円210円227円/345円215円225円350円260円203円351円230円237円345円210円227円.md" "b/346円225円260円346円215円256円347円273円223円346円236円204円347円263円273円345円210円227円/345円215円225円350円260円203円351円230円237円345円210円227円.md" index 5df39624bf..ff2e60b1c7 100644 --- "a/346円225円260円346円215円256円347円273円223円346円236円204円347円263円273円345円210円227円/345円215円225円350円260円203円351円230円237円345円210円227円.md" +++ "b/346円225円260円346円215円256円347円273円223円346円236円204円347円263円273円345円210円227円/345円215円225円350円260円203円351円230円237円345円210円227円.md" @@ -149,7 +149,7 @@ public void push(int n) { } ``` -你可以想象,加入数字的大小代表人的体重,把前面体重不足的都压扁了,直到遇到更大的量级才停住。 +你可以想象,加入数字的大小代表人的体重,体重大的会把前面体重不足的压扁,直到遇到更大的量级才停住。 ![](https://labuladong.github.io/pictures/单调队列/3.png) @@ -182,7 +182,7 @@ class MonotonicQueue { } ``` -之所以要判断 `data.getFirst() == n`,是因为我们想删除的队头元素 `n` 可能已经被「压扁」了,可能已经不存在了,所以这时候就不用删除了: +之所以要判断 `n == maxq.getFirst()`,是因为我们想删除的队头元素 `n` 可能已经被「压扁」了,可能已经不存在了,所以这时候就不用删除了: ![](https://labuladong.github.io/pictures/单调队列/2.png) diff --git "a/347円256円227円346円263円225円346円200円235円347円273円264円347円263円273円345円210円227円/BFS346円241円206円346円236円266円.md" "b/347円256円227円346円263円225円346円200円235円347円273円264円347円263円273円345円210円227円/BFS346円241円206円346円236円266円.md" index f5f1837dd0..743705da00 100644 --- "a/347円256円227円346円263円225円346円200円235円347円273円264円347円263円273円345円210円227円/BFS346円241円206円346円236円266円.md" +++ "b/347円256円227円346円263円225円346円200円235円347円273円264円347円263円273円345円210円227円/BFS346円241円206円346円236円266円.md" @@ -138,6 +138,8 @@ int minDepth(TreeNode root) { } ``` + + 这里注意这个 `while` 循环和 `for` 循环的配合,**`while` 循环控制一层一层往下走,`for` 循环利用 `sz` 变量控制从左到右遍历每一层二叉树节点**: ![](https://labuladong.github.io/pictures/dijkstra/1.jpeg) diff --git "a/347円256円227円346円263円225円346円200円235円347円273円264円347円263円273円345円210円227円/BFS350円247円243円345円206円263円346円273円221円345円212円250円346円213円274円345円233円276円.md" "b/347円256円227円346円263円225円346円200円235円347円273円264円347円263円273円345円210円227円/BFS350円247円243円345円206円263円346円273円221円345円212円250円346円213円274円345円233円276円.md" index 90968a4e35..3734409d10 100644 --- "a/347円256円227円346円263円225円346円200円235円347円273円264円347円263円273円345円210円227円/BFS350円247円243円345円206円263円346円273円221円345円212円250円346円213円274円345円233円276円.md" +++ "b/347円256円227円346円263円225円346円200円235円347円273円264円347円263円273円345円210円227円/BFS350円247円243円345円206円263円346円273円221円345円212円250円346円213円274円345円233円276円.md" @@ -97,7 +97,29 @@ int[][] neighbor = new int[][]{ 观察上图就能发现,如果二维数组中的某个元素 `e` 在一维数组中的索引为 `i`,那么 `e` 的左右相邻元素在一维数组中的索引就是 `i - 1` 和 `i + 1`,而 `e` 的上下相邻元素在一维数组中的索引就是 `i - n` 和 `i + n`,其中 `n` 为二维数组的列数。 -这样,对于 `m x n` 的二维数组,我们可以写一个函数来生成它的 `neighbor` 索引映射,篇幅所限,我这里就不写了。 +这样,对于 `m x n` 的二维数组,我们可以写一个函数来生成它的 `neighbor` 索引映射: + +```java +int[][] generateNeighborMapping(int m, int n) { + int[][] neighbor = new int[m * n][]; + for (int i = 0; i < m * n; i++) { + List neighbors = new ArrayList(); + + // 如果不是第一列,有左侧邻居 + if (i % n != 0) neighbors.add(i - 1); + // 如果不是最后一列,有右侧邻居 + if (i % n != n - 1) neighbors.add(i + 1); + // 如果不是第一行,有上方邻居 + if (i - n>= 0) neighbors.add(i - n); + // 如果不是最后一行,有下方邻居 + if (i + n < m * n) neighbors.add(i + n); + + // Java 语言特性,将 List 类型转为 int[] 数组 + neighbor[i] = neighbors.stream().mapToInt(Integer::intValue).toArray(); + } + return neighbor; +} +``` 至此,我们就把这个问题完全转化成标准的 BFS 问题了,借助前文 [BFS 算法框架](https://labuladong.github.io/article/fname.html?fname=BFS框架) 的代码框架,直接就可以套出解法代码了: diff --git "a/347円256円227円346円263円225円346円200円235円347円273円264円347円263円273円345円210円227円/UnionFind347円256円227円346円263円225円350円257円246円350円247円243円.md" "b/347円256円227円346円263円225円346円200円235円347円273円264円347円263円273円345円210円227円/UnionFind347円256円227円346円263円225円350円257円246円350円247円243円.md" index ea8e62694f..38ec1c2c61 100644 --- "a/347円256円227円346円263円225円346円200円235円347円273円264円347円263円273円345円210円227円/UnionFind347円256円227円346円263円225円350円257円246円350円247円243円.md" +++ "b/347円256円227円346円263円225円346円200円235円347円273円264円347円263円273円345円210円227円/UnionFind347円256円227円346円263円225円350円257円246円350円247円243円.md" @@ -294,7 +294,7 @@ class UF { ![](https://labuladong.github.io/pictures/unionfind/9.gif) -用语言描述就是,每次 while 循环都会把一对儿父子节点改到同一层,这样每次调用 `find` 函数向树根遍历的同时,顺手就将树高缩短了。 +用语言描述就是,每次 while 循环都会让部分子节点向上移动,这样每次调用 `find` 函数向树根遍历的同时,顺手就将树高缩短了。 路径压缩的第二种写法是这样: diff --git "a/347円256円227円346円263円225円346円200円235円347円273円264円347円263円273円345円210円227円/345円217円214円346円214円207円351円222円210円346円212円200円345円267円247円.md" "b/347円256円227円346円263円225円346円200円235円347円273円264円347円263円273円345円210円227円/345円217円214円346円214円207円351円222円210円346円212円200円345円267円247円.md" index 1c3b9d31de..0c45544085 100644 --- "a/347円256円227円346円263円225円346円200円235円347円273円264円347円263円273円345円210円227円/345円217円214円346円214円207円351円222円210円346円212円200円345円267円247円.md" +++ "b/347円256円227円346円263円225円346円200円235円347円273円264円347円263円273円345円210円227円/345円217円214円346円214円207円351円222円210円346円212円200円345円267円247円.md" @@ -128,9 +128,9 @@ ListNode deleteDuplicates(ListNode head) { ![](https://labuladong.github.io/pictures/数组去重/2.gif) -这里可能有读者会问,链表中那些重复的元素并没有被删掉,就让这些节点在链表上挂着,合适吗? - -这就要探讨不同语言的特性了,像 Java/Python 这类带有垃圾回收的语言,可以帮我们自动找到并回收这些「悬空」的链表节点的内存,而像 C++ 这类语言没有自动垃圾回收的机制,确实需要我们编写代码时手动释放掉这些节点的内存。 +> note:这里可能有读者会问,链表中那些重复的元素并没有被删掉,就让这些节点在链表上挂着,合适吗? +> +> 这就要探讨不同语言的特性了,像 Java/Python 这类带有垃圾回收的语言,可以帮我们自动找到并回收这些「悬空」的链表节点的内存,而像 C++ 这类语言没有自动垃圾回收的机制,确实需要我们编写代码时手动释放掉这些节点的内存。 不过话说回来,就算法思维的培养来说,我们只需要知道这种快慢指针技巧即可。 diff --git "a/347円256円227円346円263円225円346円200円235円347円273円264円347円263円273円345円210円227円/345円233円236円346円272円257円347円256円227円346円263円225円350円257円246円350円247円243円344円277円256円350円256円242円347円211円210円.md" "b/347円256円227円346円263円225円346円200円235円347円273円264円347円263円273円345円210円227円/345円233円236円346円272円257円347円256円227円346円263円225円350円257円246円350円247円243円344円277円256円350円256円242円347円211円210円.md" index 5b652a94d5..bb01e8ea6f 100644 --- "a/347円256円227円346円263円225円346円200円235円347円273円264円347円263円273円345円210円227円/345円233円236円346円272円257円347円256円227円346円263円225円350円257円246円350円247円243円344円277円256円350円256円242円347円211円210円.md" +++ "b/347円256円227円346円263円225円346円200円235円347円273円264円347円263円273円345円210円227円/345円233円236円346円272円257円347円256円227円346円263円225円350円257円246円350円247円243円344円277円256円350円256円242円347円211円210円.md" @@ -401,6 +401,7 @@ def backtrack(...): - [Dijkstra 算法模板及应用](https://labuladong.github.io/article/fname.html?fname=dijkstra算法) - [FloodFill算法详解及应用](https://labuladong.github.io/article/fname.html?fname=FloodFill算法详解及应用) - [base case 和备忘录的初始值怎么定?](https://labuladong.github.io/article/fname.html?fname=备忘录等基础) + - [一文秒杀所有岛屿题目](https://labuladong.github.io/article/fname.html?fname=岛屿题目) - [东哥带你刷二叉树(纲领篇)](https://labuladong.github.io/article/fname.html?fname=二叉树总结) - [二分搜索怎么用?我和快手面试官进行了深度探讨](https://labuladong.github.io/article/fname.html?fname=二分分割子数组) - [分治算法详解:运算优先级](https://labuladong.github.io/article/fname.html?fname=分治算法) diff --git "a/347円256円227円346円263円225円346円200円235円347円273円264円347円263円273円345円210円227円/345円267円256円345円210円206円346円212円200円345円267円247円.md" "b/347円256円227円346円263円225円346円200円235円347円273円264円347円263円273円345円210円227円/345円267円256円345円210円206円346円212円200円345円267円247円.md" index 595d0b1eff..932a1295fd 100644 --- "a/347円256円227円346円263円225円346円200円235円347円273円264円347円263円273円345円210円227円/345円267円256円345円210円206円346円212円200円345円267円247円.md" +++ "b/347円256円227円346円263円225円346円200円235円347円273円264円347円263円273円345円210円227円/345円267円256円345円210円206円346円212円200円345円267円247円.md" @@ -34,27 +34,28 @@ tags: ['数组', '差分数组'] ```java class PrefixSum { // 前缀和数组 - private int[] prefix; - + private int[] preSum; + /* 输入一个数组,构造前缀和 */ public PrefixSum(int[] nums) { - prefix = new int[nums.length + 1]; + // preSum[0] = 0,便于计算累加和 + preSum = new int[nums.length + 1]; // 计算 nums 的累加和 - for (int i = 1; i < prefix.length; i++) { - prefix[i] = prefix[i - 1] + nums[i - 1]; + for (int i = 1; i < preSum.length; i++) { + preSum[i] = preSum[i - 1] + nums[i - 1]; } } - - /* 查询闭区间 [i, j] 的累加和 */ - public int query(int i, int j) { - return prefix[j + 1] - prefix[i]; + + /* 查询闭区间 [left, right] 的累加和 */ + public int sumRange(int left, int right) { + return preSum[right + 1] - preSum[left]; } } ``` ![](https://labuladong.github.io/pictures/差分数组/1.jpeg) -`prefix[i]` 就代表着 `nums[0..i-1]` 所有元素的累加和,如果我们想求区间 `nums[i..j]` 的累加和,只要计算 `prefix[j+1] - prefix[i]` 即可,而不需要遍历整个区间求和。 +`preSum[i]` 就代表着 `nums[0..i-1]` 所有元素的累加和,如果我们想求区间 `nums[i..j]` 的累加和,只要计算 `preSum[j+1] - preSum[i]` 即可,而不需要遍历整个区间求和。 本文讲一个和前缀和思想非常类似的算法技巧「差分数组」,**差分数组的主要适用场景是频繁对原始数组的某个区间的元素进行增减**。 @@ -64,7 +65,7 @@ class PrefixSum { 常规的思路很容易,你让我给区间 `nums[i..j]` 加上 `val`,那我就一个 for 循环给它们都加上呗,还能咋样?这种思路的时间复杂度是 O(N),由于这个场景下对 `nums` 的修改非常频繁,所以效率会很低下。 -这里就需要差分数组的技巧,类似前缀和技巧构造的 `prefix` 数组,我们先对 `nums` 数组构造一个 `diff` 差分数组,**`diff[i]` 就是 `nums[i]` 和 `nums[i-1]` 之差**: +这里就需要差分数组的技巧,类似前缀和技巧构造的 `preSum` 数组,我们先对 `nums` 数组构造一个 `diff` 差分数组,**`diff[i]` 就是 `nums[i]` 和 `nums[i-1]` 之差**: ```java diff --git "a/351円253円230円351円242円221円351円235円242円350円257円225円347円263円273円345円210円227円/LRU347円256円227円346円263円225円.md" "b/351円253円230円351円242円221円351円235円242円350円257円225円347円263円273円345円210円227円/LRU347円256円227円346円263円225円.md" index 3742a10344..38f3acdb8e 100644 --- "a/351円253円230円351円242円221円351円235円242円350円257円225円347円263円273円345円210円227円/LRU347円256円227円346円263円225円.md" +++ "b/351円253円230円351円242円221円351円235円242円350円257円225円347円263円273円345円210円227円/LRU347円256円227円346円263円225円.md" @@ -45,7 +45,7 @@ LRU 缓存淘汰算法就是一种常用策略。LRU 的全称是 Least Recently ![](https://labuladong.github.io/pictures/LRU算法/3.jpg) -现在你应该理解 LRU(Least Recently Used)策略了。当然还有其他缓存淘汰策略,比如不要按访问的时序来淘汰,而是按访问频率(LFU 策略)来淘汰等等,各有应用场景。本文讲解 LRU 算法策略。 +现在你应该理解 LRU(Least Recently Used)策略了。当然还有其他缓存淘汰策略,比如不要按访问的时序来淘汰,而是按访问频率(LFU 策略)来淘汰等等,各有应用场景。本文讲解 LRU 算法策略,我会在 [LFU 算法详解](https://labuladong.github.io/article/fname.html?fname=LFU) 中讲解 LFU 算法。 ### 一、LRU 算法描述 diff --git "a/351円253円230円351円242円221円351円235円242円350円257円225円347円263円273円345円210円227円/344円272円214円345円210円206円350円277円220円347円224円250円.md" "b/351円253円230円351円242円221円351円235円242円350円257円225円347円263円273円345円210円227円/344円272円214円345円210円206円350円277円220円347円224円250円.md" index 8741ff52b7..66220d7fe9 100644 --- "a/351円253円230円351円242円221円351円235円242円350円257円225円347円263円273円345円210円227円/344円272円214円345円210円206円350円277円220円347円224円250円.md" +++ "b/351円253円230円351円242円221円351円235円242円350円257円225円347円263円273円345円210円227円/344円272円214円345円210円206円350円277円220円347円224円250円.md" @@ -47,6 +47,8 @@ tags: ['二分查找'] 因为算法题一般都让你求最值,比如让你求吃香蕉的「最小速度」,让你求轮船的「最低运载能力」,求最值的过程,必然是搜索一个边界的过程,所以后面我们就详细分析一下这两种搜索边界的二分算法代码。 +> note:注意,本文我写的都是左闭右开的二分搜索写法,如果你习惯两端都闭的写法,可以自行改写代码。 + 「搜索左侧边界」的二分搜索算法的具体代码实现如下: diff --git "a/351円253円230円351円242円221円351円235円242円350円257円225円347円263円273円345円210円227円/345円255円220円351円233円206円346円216円222円345円210円227円347円273円204円345円220円210円.md" "b/351円253円230円351円242円221円351円235円242円350円257円225円347円263円273円345円210円227円/345円255円220円351円233円206円346円216円222円345円210円227円347円273円204円345円220円210円.md" index a628d2623d..63deaa7ea8 100644 --- "a/351円253円230円351円242円221円351円235円242円350円257円225円347円263円273円345円210円227円/345円255円220円351円233円206円346円216円222円345円210円227円347円273円204円345円220円210円.md" +++ "b/351円253円230円351円242円221円351円235円242円350円257円225円347円263円273円345円210円227円/345円255円220円351円233円206円346円216円222円345円210円227円347円273円204円345円220円210円.md" @@ -108,7 +108,7 @@ List> subsets(int[] nums) 为什么集合 `[2]` 只需要添加 `3`,而不添加前面的 `1` 呢? -因为集合中的元素不用考虑顺序, `[1,2,3]` 中 `2` 后面只有 `3`,如果你向前考虑 `1`,那么 `[2,1]` 会和之前已经生成的子集 `[1,2]` 重复。 +因为集合中的元素不用考虑顺序,`[1,2,3]` 中 `2` 后面只有 `3`,如果你添加了前面的 `1`,那么 `[2,1]` 会和之前已经生成的子集 `[1,2]` 重复。 **换句话说,我们通过保证元素之间的相对顺序不变来防止出现重复的子集**。 diff --git "a/351円253円230円351円242円221円351円235円242円350円257円225円347円263円273円345円210円227円/345円262円233円345円261円277円351円242円230円347円233円256円.md" "b/351円253円230円351円242円221円351円235円242円350円257円225円347円263円273円345円210円227円/345円262円233円345円261円277円351円242円230円347円233円256円.md" index 65d8005f15..c82ab4f8b5 100644 --- "a/351円253円230円351円242円221円351円235円242円350円257円225円347円263円273円345円210円227円/345円262円233円345円261円277円351円242円230円347円233円256円.md" +++ "b/351円253円230円351円242円221円351円235円242円350円257円225円347円263円273円345円210円227円/345円262円233円345円261円277円351円242円230円347円233円256円.md" @@ -70,7 +70,7 @@ void dfs(int[][] grid, int i, int j, boolean[][] visited) { 因为二维矩阵本质上是一幅「图」,所以遍历的过程中需要一个 `visited` 布尔数组防止走回头路,如果你能理解上面这段代码,那么搞定所有岛屿系列题目都很简单。 -这里额外说一个处理二维数组的常用小技巧,你有时会看到使用「方向数组」来处理上下左右的遍历,和前文 [图遍历框架](https://labuladong.github.io/article/fname.html?fname=图) 的代码很类似: +这里额外说一个处理二维数组的常用小技巧,你有时会看到使用「方向数组」来处理上下左右的遍历,和前文 [union-find 算法详解](https://labuladong.github.io/article/fname.html?fname=UnionFind算法详解) 的代码很类似: ```java @@ -499,6 +499,8 @@ void dfs(int[][] grid, int i, int j, StringBuilder sb, int dir) { } ``` +> note:仔细看这个代码,在递归前做选择,在递归后撤销选择,它像不像 [回溯算法框架](https://labuladong.github.io/article/fname.html?fname=回溯算法详解修订版)?实际上它就是回溯算法,因为它关注的是「树枝」(岛屿的遍历顺序),而不是「节点」(岛屿的每个格子)。 + `dir` 记录方向,`dfs` 函数递归结束后,`sb` 记录着整个遍历顺序。有了这个 `dfs` 函数就好办了,我们可以直接写出最后的解法代码: @@ -523,7 +525,7 @@ int numDistinctIslands(int[][] grid) { } ``` -这样,这道题就解决了,至于为什么初始调用 `dfs` 函数时的 `dir` 参数可以随意写,这里涉及 DFS 和回溯算法的一个细微差别,前文 [图算法基础](https://labuladong.github.io/article/fname.html?fname=图) 有写,这里就不展开了。 +这样,这道题就解决了,至于为什么初始调用 `dfs` 函数时的 `dir` 参数可以随意写,因为这个 `dfs` 函数实际上是回溯算法,它关注的是「树枝」而不是「节点」,前文 [图算法基础](https://labuladong.github.io/article/fname.html?fname=图) 有写具体的区别,这里就不赘述了。 以上就是全部岛屿系列题目的解题思路,也许前面的题目大部分人会做,但是最后两题还是比较巧妙的,希望本文对你有帮助。

AltStyle によって変換されたページ (->オリジナル) /