This commit is contained in:
programmercarl
2022-07-22 10:39:46 +08:00
parent 8292f4a9b8
commit 1b4422d438
3 changed files with 202 additions and 1 deletions

View File

@ -14,7 +14,7 @@
编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 char[] 的形式给出。 编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 char[] 的形式给出。
不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 $O(1)$ 的额外空间解决这一问题。 不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。
你可以假设数组中的所有字符都是 ASCII 码表中的可打印字符。 你可以假设数组中的所有字符都是 ASCII 码表中的可打印字符。

View File

@ -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<vector<int>>& heights, vector<vector<bool>>& 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<vector<int>>& heights, int x, int y) {
vector<vector<bool>> visited = vector<vector<bool>>(heights.size(), vector<bool>(heights[0].size(), false));
// 深搜将x,y出发 能到的节点都标记上。
dfs(heights, visited, x, y);
bool isPacific = false;
bool isAtlantic = false;
// 以下就是判断xy出发是否到达太平洋和大西洋
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<vector<int>> pacificAtlantic(vector<vector<int>>& heights) {
vector<vector<int>> 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<vector<int>>& heights, vector<vector<bool>>& 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<vector<int>> pacificAtlantic(vector<vector<int>>& heights) {
vector<vector<int>> result;
int n = heights.size();
int m = heights[0].size(); // 这里不用担心空指针题目要求说了长宽都大于1
// 记录从太平洋边出发,可以遍历的节点
vector<vector<bool>> pacific = vector<vector<bool>>(n, vector<bool>(m, false));
// 记录从大西洋出发,可以遍历的节点
vector<vector<bool>> atlantic = vector<vector<bool>>(n, vector<bool>(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 的数组。

View File

@ -37,6 +37,8 @@
# 思路 # 思路
本题视频讲解:[学透哈希表map使用有技巧LeetCode454.四数相加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)差不多,其实差很多。
**本题是使用哈希法的经典题目,而[0015.三数之和](https://programmercarl.com/0015.三数之和.html)[0018.四数之和](https://programmercarl.com/0018.四数之和.html)并不合适使用哈希法**,因为三数之和和四数之和这两道题目使用哈希法在不超时的情况下做到对结果去重是很困难的,很有多细节需要处理。 **本题是使用哈希法的经典题目,而[0015.三数之和](https://programmercarl.com/0015.三数之和.html)[0018.四数之和](https://programmercarl.com/0018.四数之和.html)并不合适使用哈希法**,因为三数之和和四数之和这两道题目使用哈希法在不超时的情况下做到对结果去重是很困难的,很有多细节需要处理。