diff --git "a/problems/0072.\347\274\226\350\276\221\350\267\235\347\246\273.md" "b/problems/0072.\347\274\226\350\276\221\350\267\235\347\246\273.md" index a34439e608..8a877d2088 100644 --- "a/problems/0072.\347\274\226\350\276\221\350\267\235\347\246\273.md" +++ "b/problems/0072.\347\274\226\350\276\221\350\267\235\347\246\273.md" @@ -20,24 +20,20 @@ * 输入:word1 = "horse", word2 = "ros" * 输出:3 * 解释: -``` horse -> rorse (将 'h' 替换为 'r') rorse -> rose (删除 'r') rose -> ros (删除 'e') -`` * 示例 2: * 输入:word1 = "intention", word2 = "execution" * 输出:5 * 解释: -``` intention -> inention (删除 't') inention -> enention (将 'i' 替换为 'e') enention -> exention (将 'n' 替换为 'x') exention -> exection (将 'n' 替换为 'c') exection -> execution (插入 'u') -``` 提示: diff --git "a/problems/0376.\346\221\206\345\212\250\345\272\217\345\210\227.md" "b/problems/0376.\346\221\206\345\212\250\345\272\217\345\210\227.md" index 723d1d9a0f..35daf3013c 100644 --- "a/problems/0376.\346\221\206\345\212\250\345\272\217\345\210\227.md" +++ "b/problems/0376.\346\221\206\345\212\250\345\272\217\345\210\227.md" @@ -53,21 +53,109 @@ **实际操作上,其实连删除的操作都不用做,因为题目要求的是最长摆动子序列的长度,所以只需要统计数组的峰值数量就可以了(相当于是删除单一坡度上的节点,然后统计长度)** -**这就是贪心所贪的地方,让峰值尽可能的保持峰值,然后删除单一坡度上的节点**。 +**这就是贪心所贪的地方,让峰值尽可能的保持峰值,然后删除单一坡度上的节点** -本题代码实现中,还有一些技巧,例如统计峰值的时候,数组最左面和最右面是最不好统计的。 +在计算是否有峰值的时候,大家知道遍历的下标i ,计算prediff(nums[i] - nums[i-1]) 和 curdiff(nums[i+1] - nums[i]),如果`prediff < 0 && curdiff > 0` 或者 `prediff > 0 && curdiff < 0` 此时就有波动就需要统计。 -例如序列[2,5],它的峰值数量是2,如果靠统计差值来计算峰值个数就需要考虑数组最左面和最右面的特殊情况。 +这是我们思考本题的一个大题思路,但本题要考虑三种情况: -所以可以针对序列[2,5],可以假设为[2,2,5],这样它就有坡度了即preDiff = 0,如图: +1. 情况一:相同数字连续 +2. 情况二:数组首尾两端 +3. 情况三:单调坡度有平坡 + +### 情况一:相同数字连续 + +例如 [1,2,2,2,1]这样的数组,如图: + +![](https://code-thinking-1253855093.file.myqcloud.com/pics/20230106170449.png) + +它的摇摆序列长度是多少呢? **其实是长度是3**,也就是我们在删除的时候 要不删除左面的三个2,要不就删除右边的三个2。 + +如图,可以统一规则,删除左边的三个2: + +![](https://code-thinking-1253855093.file.myqcloud.com/pics/20230106172613.png) + +在图中,当i指向第一个2的时候,`prediff > 0 && curdiff = 0` ,当 i 指向最后一个2的时候 `prediff = 0 && curdiff < 0`。 + +如果我们采用,删左面三个2的规则,那么 当 `prediff = 0 && curdiff < 0` 也要记录一个峰值,因为他是把之前相同的元素都删掉留下的峰值。 + +所以我们记录峰值的条件应该是: `(preDiff <= 0 && curDiff > 0) || (preDiff >= 0 && curDiff < 0)`,为什么这里允许 prediff == 0 ,就是为了 上面我说的这种情况。 + + +### 情况二:数组首尾两端 + + +所以本题统计峰值的时候,数组最左面和最右面如果统计呢? + +题目中说了,如果只有两个不同的元素,那摆动序列也是2。 + +例如序列[2,5],如果靠统计差值来计算峰值个数就需要考虑数组最左面和最右面的特殊情况。 + +因为我们在计算 prediff(nums[i] - nums[i-1]) 和 curdiff(nums[i+1] - nums[i])的时候,至少需要三个数字才能计算,而数组只有两个数字。 + +这里我们可以写死,就是 如果只有两个元素,且元素不同,那么结果为2。 + +不写死的话,如果和我们的判断规则结合在一起呢? + +可以假设,数组最前面还有一个数字,那这个数字应该是什么呢? + +之前我们在 讨论 情况一:相同数字连续 的时候, prediff = 0 ,curdiff < 0 或者 >0 也记为波谷。 + +那么为了规则统一,针对序列[2,5],可以假设为[2,2,5],这样它就有坡度了即preDiff = 0,如图: ![376.摆动序列1](https://img-blog.csdnimg.cn/20201124174357612.png) 针对以上情形,result初始为1(默认最右面有一个峰值),此时curDiff > 0 && preDiff <= 0,那么result++(计算了左面的峰值),最后得到的result就是2(峰值个数为2即摆动序列长度为2) -C++代码如下(和上图是对应的逻辑): +经过以上分析后,我们可以写出如下代码: + +```CPP +// 版本一 +class Solution { +public: + int wiggleMaxLength(vector& nums) { + if (nums.size() <= 1) return nums.size(); + int curDiff = 0; // 当前一对差值 + int preDiff = 0; // 前一对差值 + int result = 1; // 记录峰值个数,序列默认序列最右边有一个峰值 + for (int i = 0; i < nums.size() - 1; i++) { + curDiff = nums[i + 1] - nums[i]; + // 出现峰值 + if ((preDiff <= 0 && curDiff > 0) || (preDiff >= 0 && curDiff < 0)) { + result++; + } + preDiff = curDiff; + } + return result; + } +}; +``` +* 时间复杂度:O(n) +* 空间复杂度:O(1) + +此时大家是不是发现 以上代码提交也不能通过本题? + +所以此时我们要讨论情况三! + +### 情况三:单调坡度有平坡 + +在版本一中,我们忽略了一种情况,即 如果在一个单调坡度上有平坡,例如[1,2,2,2,3,4],如图: + +![](https://code-thinking-1253855093.file.myqcloud.com/pics/20230108171505.png) + +图中,我们可以看出,版本一的代码在三个地方记录峰值,但其实结果因为是2,因为 单调中的平坡 不能算峰值(即摆动)。 + +之所以版本一会出问题,是因为我们事实更新了 prediff。 + +那么我们应该什么时候更新prediff呢? + +我们只需要在 这个坡度 摆动变化的时候,更新prediff就行,这样prediff在 单调区间有平坡的时候 就不会发生变化,造成我们的误判。 + +所以本题的最终代码为: ```CPP + +// 版本二 class Solution { public: int wiggleMaxLength(vector& nums) { @@ -78,9 +166,9 @@ public: for (int i = 0; i < nums.size() - 1; i++) { curDiff = nums[i + 1] - nums[i]; // 出现峰值 - if ((curDiff > 0 && preDiff <= 0) || (preDiff >= 0 && curDiff < 0)) { + if ((preDiff <= 0 && curDiff > 0) || (preDiff >= 0 && curDiff < 0)) { result++; - preDiff = curDiff; + preDiff = curDiff; // 注意这里,只在摆动变化的时候更新prediff } } return result; @@ -88,8 +176,11 @@ public: }; ``` -* 时间复杂度:O(n) -* 空间复杂度:O(1) +其实本题看起来好像简单,但需要考虑的情况还是很复杂的,而且很难一次性想到位。 + +**本题异常情况的本质,就是要考虑平坡**, 平坡分两种,一个是 上下中间有平坡,一个是单调有平坡,如图: + +![](https://code-thinking-1253855093.file.myqcloud.com/pics/20230108174452.png) ## 思路2(动态规划) @@ -111,25 +202,19 @@ public: C++代码如下: -```c++ +```CPP class Solution { public: int dp[1005][2]; int wiggleMaxLength(vector& nums) { memset(dp, 0, sizeof dp); dp[0][0] = dp[0][1] = 1; - - for (int i = 1; i < nums.size(); ++i) - { + for (int i = 1; i < nums.size(); ++i) { dp[i][0] = dp[i][1] = 1; - - for (int j = 0; j < i; ++j) - { + for (int j = 0; j < i; ++j) { if (nums[j] > nums[i]) dp[i][1] = max(dp[i][1], dp[j][0] + 1); } - - for (int j = 0; j < i; ++j) - { + for (int j = 0; j < i; ++j) { if (nums[j] < nums[i]) dp[i][0] = max(dp[i][0], dp[j][1] + 1); } } @@ -153,17 +238,6 @@ public: 空间复杂度:O(n) -## 总结 - -**贪心的题目说简单有的时候就是常识,说难就难在都不知道该怎么用贪心**。 - -本题大家如果要去模拟删除元素达到最长摆动子序列的过程,那指定绕里面去了,一时半会拔不出来。 - -而这道题目有什么技巧说一下子能想到贪心么? - -其实也没有,类似的题目做过了就会想到。 - -此时大家就应该了解了:保持区间波动,只需要把单调区间上的元素移除就可以了。 diff --git "a/problems/0827.\346\234\200\345\244\247\344\272\272\345\267\245\345\262\233.md" "b/problems/0827.\346\234\200\345\244\247\344\272\272\345\267\245\345\262\233.md" index fb0c797eb6..05d82f7e65 100644 --- "a/problems/0827.\346\234\200\345\244\247\344\272\272\345\267\245\345\262\233.md" +++ "b/problems/0827.\346\234\200\345\244\247\344\272\272\345\267\245\345\262\233.md" @@ -3,8 +3,11 @@

参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!

+ # 827. 最大人工岛 +[力扣链接](https://leetcode.cn/problems/making-a-large-island/) + 给你一个大小为 n x n 二进制矩阵 grid 。最多 只能将一格 0 变成 1 。 返回执行此操作后,grid 中最大的岛屿面积是多少? @@ -30,7 +33,9 @@ 本题的一个暴力想法,应该是遍历地图尝试 将每一个 0 改成1,然后去搜索地图中的最大的岛屿面积。 -计算地图的最大面积:遍历地图 + 深搜岛屿,时间复杂度为 n * n +计算地图的最大面积:遍历地图 + 深搜岛屿,时间复杂度为 n * n。 + +(其实使用深搜还是广搜都是可以的,其目的就是遍历岛屿做一个标记,相当于染色,那么使用哪个遍历方式都行,以下我用深搜来讲解) 每改变一个0的方格,都需要重新计算一个地图的最大面积,所以 整体时间复杂度为:n^4。 @@ -41,7 +46,7 @@ 其实每次深搜遍历计算最大岛屿面积,我们都做了很多重复的工作。 -只要把深搜就可以并每个岛屿的面积记录下来就好。 +只要用一次深搜把每个岛屿的面积记录下来就好。 第一步:一次遍历地图,得出各个岛屿的面积,并做编号记录。可以使用map记录,key为岛屿编号,value为岛屿面积 第二步:在遍历地图,遍历0的方格(因为要将0变成1),并统计该1(由0变成的1)周边岛屿面积,将其相邻面积相加在一起,遍历所有 0 之后,就可以得出 选一个0变成1 之后的最大面积。 @@ -74,7 +79,7 @@ void dfs(vector>& grid, vector>& visited, int x, int y, int largestIsland(vector>& grid) { int n = grid.size(), m = grid[0].size(); - vector> visited = vector>(n, vector(m, false)); + vector> visited = vector>(n, vector(m, false)); // 标记访问过的点 unordered_map gridNum; int mark = 2; // 记录每个岛屿的编号 bool isAllGrid = true; // 标记是否整个地图都是陆地 @@ -92,9 +97,10 @@ int largestIsland(vector>& grid) { } ``` + 这个过程时间复杂度 n * n 。可能有录友想:分明是两个for循环下面套这一个dfs,时间复杂度怎么回事 n * n呢? -其实大家可以自己看代码的时候,**n * n这个方格地图中,每个节点我们就遍历一次,并不会重复遍历**。 +其实大家可以仔细看一下代码,**n * n这个方格地图中,每个节点我们就遍历一次,并不会重复遍历**。 第二步过程如图所示: @@ -106,6 +112,47 @@ int largestIsland(vector>& grid) { 所以整个解法的时间复杂度,为 n * n + n * n 也就是 n^2。 +当然这里还有一个优化的点,就是 可以不用 visited数组,因为有mark来标记,所以遍历过的grid[i][j]是不等于1的。 + +代码如下: + +```CPP + int dir[4][2] = {0, 1, 1, 0, -1, 0, 0, -1}; // 四个方向 + void dfs(vector>& grid, int x, int y, int mark) { + if (grid[x][y] != 1 || grid[x][y] == 0) return; // 终止条件:访问过的节点 或者 遇到海水 + grid[x][y] = mark; // 给陆地标记新标签 + count++; + for (int i = 0; i < 4; i++) { + int nextx = x + dir[i][0]; + int nexty = y + dir[i][1]; + if (nextx < 0 || nextx >= grid.size() || nexty < 0 || nexty >= grid[0].size()) continue; // 越界了,直接跳过 + dfs(grid, nextx, nexty, mark); + } + } + +public: + int largestIsland(vector>& grid) { + int n = grid.size(), m = grid[0].size(); + unordered_map gridNum; + int mark = 2; // 记录每个岛屿的编号 + bool isAllGrid = true; // 标记是否整个地图都是陆地 + for (int i = 0; i < n; i++) { + for (int j = 0; j < m; j++) { + if (grid[i][j] == 0) isAllGrid = false; + if (grid[i][j] == 1) { + count = 0; + dfs(grid, i, j, mark); // 将与其链接的陆地都标记上 true + gridNum[mark] = count; // 记录每一个岛屿的面积 + mark++; // 记录下一个岛屿编号 + } + } + } + } +} +``` + +不过为了让各个变量各司其事,代码清晰一些,完整代码还是使用visited数组来标记。 + 最后,整体代码如下: ```CPP @@ -129,7 +176,7 @@ private: public: int largestIsland(vector>& grid) { int n = grid.size(), m = grid[0].size(); - vector> visited = vector>(n, vector(m, false)); + vector> visited = vector>(n, vector(m, false)); // 标记访问过的点 unordered_map gridNum; int mark = 2; // 记录每个岛屿的编号 bool isAllGrid = true; // 标记是否整个地图都是陆地 @@ -171,6 +218,7 @@ public: } }; ``` +

diff --git "a/problems/1020.\351\243\236\345\234\260\347\232\204\346\225\260\351\207\217.md" "b/problems/1020.\351\243\236\345\234\260\347\232\204\346\225\260\351\207\217.md" index 8b54d7f1c0..b7cba7a6a1 100644 --- "a/problems/1020.\351\243\236\345\234\260\347\232\204\346\225\260\351\207\217.md" +++ "b/problems/1020.\351\243\236\345\234\260\347\232\204\346\225\260\351\207\217.md" @@ -6,6 +6,8 @@ # 1020. 飞地的数量 +[力扣链接](https://leetcode.cn/problems/number-of-enclaves/description/) + 给你一个大小为 m x n 的二进制矩阵 grid ,其中 0 表示一个海洋单元格、1 表示一个陆地单元格。 一次 移动 是指从一个陆地单元格走到另一个相邻(上、下、左、右)的陆地单元格或跨过 grid 的边界。 @@ -26,7 +28,7 @@ ## 思路 -本题使用dfs,bfs,并查集都是可以的。 本题和 417. 太平洋大西洋水流问题 很像。 +本题使用dfs,bfs,并查集都是可以的。 本题要求找到不靠边的陆地面积,那么我们只要从周边找到陆地然后 通过 dfs或者bfs 将周边靠陆地且相邻的陆地都变成海洋,然后再去重新遍历地图的时候,统计此时还剩下的陆地就可以了。