diff --git "a/problems/0344.\345\217\215\350\275\254\345\255\227\347\254\246\344\270\262.md" "b/problems/0344.\345\217\215\350\275\254\345\255\227\347\254\246\344\270\262.md" index 031bc0adbd..ebadc04486 100644 --- "a/problems/0344.\345\217\215\350\275\254\345\255\227\347\254\246\344\270\262.md" +++ "b/problems/0344.\345\217\215\350\275\254\345\255\227\347\254\246\344\270\262.md" @@ -14,7 +14,7 @@ 编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 char[] 的形式给出。 -不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 $O(1)$ 的额外空间解决这一问题。 +不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。 你可以假设数组中的所有字符都是 ASCII 码表中的可打印字符。 diff --git "a/problems/0417.\345\244\252\345\271\263\346\264\213\345\244\247\350\245\277\346\264\213\346\260\264\346\265\201\351\227\256\351\242\230.md" "b/problems/0417.\345\244\252\345\271\263\346\264\213\345\244\247\350\245\277\346\264\213\346\260\264\346\265\201\351\227\256\351\242\230.md" new file mode 100644 index 0000000000..7efee80ade --- /dev/null +++ "b/problems/0417.\345\244\252\345\271\263\346\264\213\345\244\247\350\245\277\346\264\213\346\260\264\346\265\201\351\227\256\351\242\230.md" @@ -0,0 +1,199 @@ +# 417. 太平洋大西洋水流问题 + +[题目链接](https://leetcode.cn/problems/pacific-atlantic-water-flow/) + +不少同学可能被这道题的题目描述迷惑了,其实就是找到哪些点 可以同时到达太平洋和大西洋。 流动的方式只能从高往低流。 + +那么一个比较直白的想法,其实就是 遍历每个点,然后看这个点 能不能同时到达太平洋和大西洋。 + +至于遍历方式,可以用dfs,也可以用bfs,以下用dfs来举例。 + +那么这种思路的实现代码如下: + +```CPP +class Solution { +private: + int dir[4][2] = {-1, 0, 0, -1, 1, 0, 0, 1}; + void dfs(vector>& heights, vector>& visited, int x, int y) { + if (visited[x][y]) return; + + visited[x][y] = true; + + for (int i = 0; i < 4; i++) { + int nextx = x + dir[i][0]; + int nexty = y + dir[i][1]; + if (nextx < 0 || nextx >= heights.size() || nexty < 0 || nexty >= heights[0].size()) continue; + if (heights[x][y] < heights[nextx][nexty]) continue; // 高度不合适 + + dfs (heights, visited, nextx, nexty); + } + return; + } + bool isResult(vector>& heights, int x, int y) { + vector> visited = vector>(heights.size(), vector(heights[0].size(), false)); + + // 深搜,将x,y出发 能到的节点都标记上。 + dfs(heights, visited, x, y); + bool isPacific = false; + bool isAtlantic = false; + + // 以下就是判断x,y出发,是否到达太平洋和大西洋 + for (int j = 0; j < heights[0].size(); j++) { + if (visited[0][j]) { + isPacific = true; + break; + } + } + for (int i = 0; i < heights.size(); i++) { + if (visited[i][0]) { + isPacific = true; + break; + } + } + for (int j = 0; j < heights[0].size(); j++) { + if (visited[heights.size() - 1][j]) { + isAtlantic = true; + break; + } + } + for (int i = 0; i < heights.size(); i++) { + if (visited[i][heights[0].size() - 1]) { + isAtlantic = true; + break; + } + } + if (isAtlantic && isPacific) return true; + return false; + } +public: + + vector> pacificAtlantic(vector>& heights) { + vector> result; + // 遍历每一个点,看是否能同时到达太平洋和大西洋 + for (int i = 0; i < heights.size(); i++) { + for (int j = 0; j < heights[0].size(); j++) { + if (isResult(heights, i, j)) result.push_back({i, j}); + } + } + return result; + } +}; + +``` + +这种思路很直白,但很明显,以上代码超时了。 来看看时间复杂度。 + +遍历每一个节点,是 m * n,遍历每一个节点的时候,都要做深搜,深搜的时间复杂度是: m * n + +那么整体时间复杂度 就是 O(m^2 * n^2) ,这是一个四次方的时间复杂度。 + +## 优化 + +那么我们可以 反过来想,从太平洋边上的节点 逆流而上,将遍历过的节点都标记上。 从大西洋的边上节点 逆流而长,讲遍历过的节点也标记上。 + +从太平洋边上节点出发,如图: + +![图一](https://code-thinking-1253855093.file.myqcloud.com/pics/20220722103029.png) + +从大西洋边上节点出发,如图: + +![图二](https://code-thinking-1253855093.file.myqcloud.com/pics/20220722103330.png) + +按照这样的逻辑,就可以写出如下遍历代码:(详细注释) + +(如果对dfs基础内容就不懂,建议看 [「代码随想录」DFS算法精讲!](https://leetcode.cn/problems/all-paths-from-source-to-target/solution/by-carlsun-2-66pf/),还可以顺便解决 797. 所有可能的路径) + +```CPP +class Solution { +private: + int dir[4][2] = {-1, 0, 0, -1, 1, 0, 0, 1}; // 保存四个方向 + + // 从低向高遍历,注意这里visited是引用,即可以改变传入的pacific和atlantic的值 + void dfs(vector>& heights, vector>& visited, int x, int y) { + if (visited[x][y]) return; + visited[x][y] = true; + for (int i = 0; i < 4; i++) { // 向四个方向遍历 + int nextx = x + dir[i][0]; + int nexty = y + dir[i][1]; + // 超过边界 + if (nextx < 0 || nextx >= heights.size() || nexty < 0 || nexty >= heights[0].size()) continue; + // 高度不合适,注意这里是从低向高判断 + if (heights[x][y] > heights[nextx][nexty]) continue; + + dfs (heights, visited, nextx, nexty); + } + return; + + } + +public: + + vector> pacificAtlantic(vector>& heights) { + vector> result; + int n = heights.size(); + int m = heights[0].size(); // 这里不用担心空指针,题目要求说了长宽都大于1 + + // 记录从太平洋边出发,可以遍历的节点 + vector> pacific = vector>(n, vector(m, false)); + + // 记录从大西洋出发,可以遍历的节点 + vector> atlantic = vector>(n, vector(m, false)); + + // 从最上最下行的节点出发,向高处遍历 + for (int i = 0; i < n; i++) { + dfs (heights, pacific, i, 0); // 遍历最上行,接触太平洋 + dfs (heights, atlantic, i, m - 1); // 遍历最下行,接触大西洋 + } + + // 从最左最右列的节点出发,向高处遍历 + for (int j = 0; j < m; j++) { + dfs (heights, pacific, 0, j); // 遍历最左列,接触太平洋 + dfs (heights, atlantic, n - 1, j); // 遍历最右列,接触大西洋 + } + for (int i = 0; i < n; i++) { + for (int j = 0; j < m; j++) { + // 如果这个节点,从太平洋和大西洋出发都遍历过,就是结果 + if (pacific[i][j] && atlantic[i][j]) result.push_back({i, j}); + } + } + return result; + } +}; + +``` + +时间复杂度分析, 关于dfs函数搜索的过程 时间复杂度是 O(n * m),这个大家比较容易想。 + +关键看主函数,那么每次dfs的时候,上面还是有for循环的。 + +第一个for循环,时间复杂度是:n * (n * m) 。 + +第二个for循环,时间复杂度是:m * (n * m)。 + +所以本题看起来 时间复杂度好像是 : n * (n * m) + m * (n * m) = (m * n) * (m + n) 。 + +其实这是一个误区,大家再自己看 dfs函数的实现,其实 有visited函数记录 走过的节点,而走过的节点是不会再走第二次的。 + +所以 调用dfs函数,**只要参数传入的是 数组pacific,那么地图中 每一个节点其实就遍历一次,无论你调用多少次**。 + +同理,调用 dfs函数,只要 参数传入的是 数组atlantic,地图中每个节点也只会遍历一次。 + +所以,以下这段代码的时间复杂度是 2 * n * m。 地图用每个节点就遍历了两次,参数传入pacific的时候遍历一次,参数传入atlantic的时候遍历一次。 +```CPP +// 从最上最下行的节点出发,向高处遍历 +for (int i = 0; i < n; i++) { + dfs (heights, pacific, i, 0); // 遍历最上行,接触太平洋 + dfs (heights, atlantic, i, m - 1); // 遍历最下行,接触大西洋 +} + +// 从最左最右列的节点出发,向高处遍历 +for (int j = 0; j < m; j++) { + dfs (heights, pacific, 0, j); // 遍历最左列,接触太平洋 + dfs (heights, atlantic, n - 1, j); // 遍历最右列,接触大西洋 +} +``` + +那么本题整体的时间复杂度其实是: 2 * n * m + n * m ,所以最终时间复杂度为 O(n * m) 。 + +空间复杂度为:O(n * m) 这个就不难理解了。开了几个 n * m 的数组。 + diff --git "a/problems/0454.\345\233\233\346\225\260\347\233\270\345\212\240II.md" "b/problems/0454.\345\233\233\346\225\260\347\233\270\345\212\240II.md" index b735c106a8..45f27a1c8d 100644 --- "a/problems/0454.\345\233\233\346\225\260\347\233\270\345\212\240II.md" +++ "b/problems/0454.\345\233\233\346\225\260\347\233\270\345\212\240II.md" @@ -37,6 +37,8 @@ # 思路 +本题视频讲解:[学透哈希表,map使用有技巧!LeetCode:454.四数相加II](https://www.bilibili.com/video/BV1Md4y1Q7Yh),结合视频在看本题解,事半功倍。 + 本题咋眼一看好像和[0015.三数之和](https://programmercarl.com/0015.三数之和.html),[0018.四数之和](https://programmercarl.com/0018.四数之和.html)差不多,其实差很多。 **本题是使用哈希法的经典题目,而[0015.三数之和](https://programmercarl.com/0015.三数之和.html),[0018.四数之和](https://programmercarl.com/0018.四数之和.html)并不合适使用哈希法**,因为三数之和和四数之和这两道题目使用哈希法在不超时的情况下做到对结果去重是很困难的,很有多细节需要处理。