一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第27天,点击查看活动详情。
每日刷题 2021.04.27
- leetcode原题链接:leetcode-cn.com/problems/pa…
- 难度:中等
- 方法:多源dfs(逆向思维)
题目
- 有一个 m × n 的矩形岛屿,与 太平洋 和 大西洋 相邻。 “太平洋” 处于大陆的左边界和上边界,而 “大西洋” 处于大陆的右边界和下边界。
- 这个岛被分割成一个由若干方形单元格组成的网格。给定一个 m x n 的整数矩阵 heights , heights[r][c] 表示坐标 (r, c) 上单元格 高于海平面的高度 。
- 岛上雨水较多,如果相邻单元格的高度 小于或等于 当前单元格的高度,雨水可以直接向北、南、东、西流向相邻单元格。水可以从海洋附近的任何单元格流入海洋。
- 返回网格坐标 result 的 2D 列表 ,其中 result[i] = [ri, ci] 表示雨水从单元格 (ri, ci) 流动 既可流向太平洋也可流向大西洋 。
示例
- 示例1
输入: heights = [[1,2,2,3,5],[3,2,3,4,4],[2,4,5,3,1],[6,7,1,4,5],[5,1,1,2,4]]
输出: [[0,4],[1,3],[1,4],[2,2],[3,0],[3,1],[4,0]]
- 示例2
输入: heights = [[2,1],[1,2]]
输出: [[0,0],[0,1],[1,0],[1,1]]
提示
m == heights.length
n == heights[r].length
1 <= m, n <= 200
0 <= heights[r][c] <= 10^5
解题思路
- 分析题目含义: 需要查找能够到达两个洋的节点(分别能够到达两个大洋的相交节点)
最开始的思路
dfs常规题,就是板子题。- 主要的问题在:如何记录:节点流入大洋中,位于当前的最后一个节点,如何将这一路可以流下来的节点记录下来??
- 现在看来,其实可以通过判断:当前节点的横纵坐标来判断其是否可以流入大洋,并且判断流入的是哪个大洋。
思路错误分析
- 误以为:只能从左上角和右下角的两个节点往回进行遍历,所能到的节点就是答案。(❌错误一直没有发现的原因,根本没有动手实践自己的想法是否正确)=> 即:同时能够到达两个大洋的节点
正确的思路
- 记
m = lenR ,n = lenC - 正向思维:
dfs遍历heights中的每一个元素,将每一个遍历的节点存储在栈中。如果路径最后找到可以入洋的节点,那么就将栈中存储的所有的节点加入到结果数组中;如果最后一个节点不能入洋,那么就从栈中将其删除,返回上一层继续查找。- 还存在一种情况:就是如果当前节点是入洋的节点,并且已经被其他节点标记访问过,那么就需要额外的处理。可以声明一个
入洋节点的数组,判断是否为入洋节点。
- 还存在一种情况:就是如果当前节点是入洋的节点,并且已经被其他节点标记访问过,那么就需要额外的处理。可以声明一个
- 逆向思维:遍历能够流入太平洋和大西洋的节点,倒着推能够流入当前节点的节点。**从入洋的节点开始遍历,可以实现对每条回溯路径的查找,反过来的话,可能会出一些问题,比如入洋节点已经被其他路径遍历过,
visited已经为true,那么下一次其他节点再遍历有可能会误以为不能入洋。**因此正向思维比较复杂,可以逆转思维想想。- 能够流入太平洋的节点:
x == 0 && y [0 ~ lenC - 1] || y == 0 && x [0 ~ lenC - 1] - 能够流入大西洋的节点:
y == lenC - 1 && x [0 ~ lenR - 1] || x == lenR - 1 && y [0 ~ lenC - 1] - 最后取能够流入太平洋的节点 与 能够流入大西洋的节点的交集。
- 能够流入太平洋的节点:
AC代码
var pacificAtlantic = function(heights) {
// 对于每一个节点,需要先判断:其自身是否可以从两个方向流出,如果可以的话,那么就记录下节点坐标,或者使用标记数组,最后循环输出
// 然后再去遍历,当前这个节点可以走的下一个节点
// 一次循环就可以,直到下标不符合就返回
// dfs, 多源dfs
// 书写dfs三步骤:
// 1.临界条件和参数:下标的范围、无返回值、参数传递当前可走的元素
// 2.单层的循环:
let lenR = heights.length,lenC = heights[0].length;
let road = new Array(lenR).fill(0).map(() => new Array(lenC).fill(0));
let visited = new Array(lenR).fill(false).map(() => new Array(lenC).fill(false));
// 可能倒着想,就不会存在这种问题了,倒着想只需要考虑能走通,就可以打标记
let nx = [-1,0,1,0],ny = [0,1,0,-1];
function dfs (x, y) {
// 判断当前的元素是否可以同时流向太平洋和大西洋,判断条件:
// 应该将其路径上走过的所有符合的节点,全部都标记出来
// 也就是说,当找到出口的时候,当前这条路全部标记,可以使用数组来处理
for(let i = 0,j = 0;i < 4; i++,j++) {
let newX = x + nx[i];
let newY = y + ny[j];
// 往旁边流
if(newX >= 0 && newX < lenR && newY >= 0 && newY < lenC && !visited[newX][newY] && heights[x][y] <= heights[newX][newY]){
road[newX][newY]++;
visited[newX][newY] = true;
dfs(newX, newY);
}
}
}
// 搜两遍,重复的输出即可
for(let j = 0; j < lenC; j++) {
if(!visited[0][j]){
visited[0][j] = true;
road[0][j]++;
dfs(0, j);
}
}
for(let i = 0; i < lenR; i++) {
if(!visited[i][0]){
visited[i][0] = true;
road[i][0]++;
dfs(i, 0);
}
}
visited = new Array(lenR).fill(false).map(() => new Array(lenC).fill(false));
for(let j = 0; j < lenC; j++) {
if(!visited[lenR - 1][j]){
visited[lenR - 1][j] = true;
road[lenR - 1][j]++;
dfs(lenR - 1, j);
}
}
for(let i = 0; i < lenR; i++) {
if(!visited[i][lenC - 1]){
visited[i][lenC - 1] = true;
road[i][lenC - 1]++;
dfs(i, lenC - 1);
}
}
let res = [];
for(let i = 0;i < lenR; i++) {
for(let j = 0; j < lenC; j++) {
if(road[i][j] == 2) res.push([i,j]);
}
}
return res;
};
总结
- 切记眼高手低,所有的样例都需要能够通过自己所想的方法验证一下,答案是否一样,再开始上手写代码。