本文已参与「新人创作礼」活动,一起开启掘金创作之路。
| 每日一题做题记录,参考官方和三叶的题解 |
题目要求
阅读理解
读了好几遍都没理解是啥意思,然后切到英文看到了both,哦那个加粗的和,好的吧……是求既可以流向太平洋又可以流向大西洋的点,一个图遍历题。
思路一:BFS
- 逆向思维,从海边推到中心;
- 分别处理可以流向Pacific和Atlantic的点,将待遍历点压入队列处理:
- 先找到所有靠海点压入队列;
- 依次判断该点周围的四个点能否流向自己、最终流向相应海洋。
Java
class Solution {
int n, m;
int[][] heights;
public List<List<Integer>> pacificAtlantic(int[][] heights) {
this.heights = heights;
m = heights.length;
n = heights[0].length;
Deque<int[]> quePac = new ArrayDeque<>(), queAtl = new ArrayDeque<>();
boolean[][] resP = new boolean[m][n], resA = new boolean[m][n];
for(int i = 0; i < m; i++) {
for(int j = 0; j < n; j++) {
if(i == 0 || j == 0) { // 上边界和左边界,与Pacific连通
resP[i][j] = true;
quePac.addLast(new int[]{i, j});
}
if(i == m -1 || j == n - 1) { // 下边界和右边界,与Atlantic连通
resA[i][j] = true;
queAtl.addLast(new int[]{i, j});
}
}
}
BFS(quePac, resP);
BFS(queAtl, resA);
List<List<Integer>> res = new ArrayList<>();
for(int i = 0; i < m; i++) {
for(int j = 0; j < n; j++) {
if(resP[i][j] && resA[i][j]) { // 都能流到
List<Integer> tmp = new ArrayList<>();
tmp.add(i);
tmp.add(j);
res.add(tmp);
}
}
}
return res;
}
int[][] dirs = new int[][]{{-1,0}, {0,-1}, {1,0}, {0,1}};
void BFS(Deque<int[]> que, boolean[][] res) {
while(!que.isEmpty()) {
int[] cur = que.pollFirst();
int x = cur[0], y = cur[1];
for(int[] d : dirs) { // 东南西北
int nx = x + d[0], ny = y + d[1];
if(nx < 0 || nx >= m || ny < 0 || ny >= n) // 超出边界
continue;
if(res[nx][ny] || heights[nx][ny] < heights[x][y]) // 已遍历或低于当前
continue;
que.addLast(new int[]{nx, ny});
res[nx][ny] = true;
}
}
}
}
- 时间复杂度:
- 空间复杂度:
C++
class Solution {
int n, m;
vector<vector<int>> heights;
const int dirs[4][2] = {{-1,0}, {0,-1}, {1,0}, {0,1}};
public:
vector<vector<int>> pacificAtlantic(vector<vector<int>>& heights) {
this->heights = heights;
m = heights.size();
n = heights[0].size();
queue<pair<int, int>> quePac, queAtl;
vector<vector<bool>> resP(m, vector<bool>(n, false));
vector<vector<bool>> resA(m, vector<bool>(n, false));
for(int i = 0; i < m; i++) {
for(int j = 0; j < n; j++) {
if(i == 0 || j == 0) { // 上边界和左边界,与Pacific连通
resP[i][j] = true;
quePac.emplace(i, j);
}
if(i == m - 1 || j == n - 1) { // 下边界和右边界,与Atlantic连通
resA[i][j] = true;
queAtl.emplace(i, j);
}
}
}
BFS(quePac, resP);
BFS(queAtl, resA);
vector<vector<int>> res;
for(int i = 0; i < m; i++) {
for(int j = 0; j < n; j++) {
if(resP[i][j] && resA[i][j]) { // 都能流到
vector<int> tmp;
tmp.emplace_back(i);
tmp.emplace_back(j);
res.emplace_back(tmp);
}
}
}
return res;
}
void BFS(queue<pair<int, int>> que, vector<vector<bool>> &res) { // 地址符忘记了,搞死我
while(!que.empty()) {
auto [x, y] = que.front();
que.pop();
for(auto d : dirs) { // 东南西北
int nx = x + d[0], ny = y + d[1];
if(nx < 0 || nx >= m || ny < 0 || ny >= n) // 超出边界
continue;
if(res[nx][ny] || heights[nx][ny] < heights[x][y]) // 已遍历或低于当前
continue;
que.emplace(nx, ny);
res[nx][ny] = true;
}
}
}
};
- 时间复杂度:
- 空间复杂度:
思路二:DFS
- 逆向思维,从海边推到中心;
- 分别处理可以流向Pacific和Atlantic的点:
- 判断当前点是否在海边;
- 判断海边点周围四个点能否流向自己,最终流向海洋;
- 依次判断相邻、相邻的相邻……
Java
class Solution {
int n, m;
int[][] heights;
public List<List<Integer>> pacificAtlantic(int[][] heights) {
this.heights = heights;
m = heights.length;
n = heights[0].length;
boolean[][] resP = new boolean[m][n], resA = new boolean[m][n];
for(int i = 0; i < m; i++) {
for(int j = 0; j < n; j++) {
if(i == 0 || j == 0) // 上边界和左边界,与Pacific连通
if(!resP[i][j])
DFS(i, j, resP);
if(i == m -1 || j == n - 1) // 下边界和右边界,与Atlantic连通
if(!resA[i][j])
DFS(i, j, resA);
}
}
List<List<Integer>> res = new ArrayList<>();
for(int i = 0; i < m; i++) {
for(int j = 0; j < n; j++) {
if(resP[i][j] && resA[i][j]) { // 都能流到
List<Integer> tmp = new ArrayList<>();
tmp.add(i);
tmp.add(j);
res.add(tmp);
}
}
}
return res;
}
int[][] dirs = new int[][]{{-1,0}, {0,-1}, {1,0}, {0,1}};
void DFS(int x, int y, boolean[][] res) {
res[x][y] = true;
for(int[] d : dirs) {
int nx = x + d[0], ny = y + d[1];
if(nx < 0 || nx >= m || ny < 0 || ny >= n) //超出边界
continue;
if(res[nx][ny] || heights[nx][ny] < heights[x][y]) // 已遍历或低于当前
continue;
DFS(nx, ny, res);
}
}
}
- 时间复杂度:
- 空间复杂度:
C++
class Solution {
int n, m;
vector<vector<int>> heights;
const int dirs[4][2] = {{-1,0}, {0,-1}, {1,0}, {0,1}};
public:
vector<vector<int>> pacificAtlantic(vector<vector<int>>& heights) {
this->heights = heights;
m = heights.size();
n = heights[0].size();
vector<vector<bool>> resP(m, vector<bool>(n, false));
vector<vector<bool>> resA(m, vector<bool>(n, false));
for(int i = 0; i < m; i++) {
for(int j = 0; j < n; j++) {
if(i == 0 || j == 0) // 上边界和左边界,与Pacific连通
if(!resP[i][j])
DFS(i, j, resP);
if(i == m - 1 || j == n - 1) // 下边界和右边界,与Atlantic连通
if(!resA[i][j])
DFS(i, j, resA);
}
}
vector<vector<int>> res;
for(int i = 0; i < m; i++) {
for(int j = 0; j < n; j++) {
if(resP[i][j] && resA[i][j]) { // 都能流到
vector<int> tmp;
tmp.emplace_back(i);
tmp.emplace_back(j);
res.emplace_back(tmp);
}
}
}
return res;
}
void DFS(int x, int y, vector<vector<bool>> &res) { // 地址符忘记了,搞死我
res[x][y] = true;
for(auto d : dirs) { // 东南西北
int nx = x + d[0], ny = y + d[1];
if(nx < 0 || nx >= m || ny < 0 || ny >= n) // 超出边界
continue;
if(res[nx][ny] || heights[nx][ny] < heights[x][y]) // 已遍历或低于当前
continue;
DFS(nx, ny, res);
}
}
};
- 时间复杂度:
- 空间复杂度:
思路三:并查集+DFS
- 用并查集维护与海的连通,当然也可以与BFS结合,在此省略。
- 能流向同一片海的,指向同一个根。
并查集
- 学习参考链接
- 处理不相交集合的合并与查询,用数组处理森林,相同集合内的元素为树形,向上指最终到根,根据两个元素指向的根可判断二者是否属于同一集合。
- 实际可以理解为认爸找爸的问题,合并两个集合,即二者认同一个爸;查询是否属于同一集合,即查询是否是一个爸。
Java
class Solution {
int N = 200 * 200 + 10;
int[] pac = new int[N], atl = new int[N];
int m, n;
int P, A;
int[][] heights;
public List<List<Integer>> pacificAtlantic(int[][] heights) {
this.heights = heights;
m = heights.length;
n = heights[0].length;
int tot = m * n;
P = tot + 1; // 流向pacific集合的爸
A = tot + 2; // 流向atlanti集合的爸
for(int i = 0; i <= A; i++)
pac[i] = atl[i] = i;
for(int i = 0; i < m; i++) {
for(int j = 0; j < n; j++) {
int idx = getIdx(i, j);
if(i == 0 || j == 0) // 上边界和左边界,与Pacific连通
if(!query(pac, P, idx))
DFS(pac, P, i, j);
if(i == m - 1 || j == n - 1) // 下边界和右边界,与Atlantic连通
if(!query(atl, A, idx))
DFS(atl, A, i, j);
}
}
List<List<Integer>> res = new ArrayList<>();
for(int i = 0; i < m; i++) {
for(int j = 0; j < n; j++) {
int idx = getIdx(i, j);
if(query(pac, P, idx) && query(atl, A, idx)) { // 都能流到
List<Integer> tmp = new ArrayList<>();
tmp.add(i);
tmp.add(j);
res.add(tmp);
}
}
}
return res;
}
int[][] dirs = new int[][]{{1,0},{-1,0},{0,1},{0,-1}};
void DFS(int[] ocean, int uf, int x, int y) {
union(ocean, uf, getIdx(x, y));
for(int[] d : dirs) { // 东南西北
int nx = x + d[0], ny = y + d[1];
if(nx < 0 || nx >= m || ny < 0 || ny >= n) // 超出边界
continue;
if(query(ocean, uf, getIdx(nx, ny)) || heights[nx][ny] < heights[x][y]) // 已遍历或低于当前
continue;
DFS(ocean, uf, nx, ny);
}
}
// 并查集
void union(int[] ocean, int a, int b) { // 并,认爸
ocean[find(ocean, a)] = ocean[find(ocean, b)];
}
int find(int[] ocean, int x) { // 查,找爸
if(ocean[x] != x)
ocean[x] = find(ocean, ocean[x]);
return ocean[x];
}
boolean query(int[] ocean, int a, int b) { // 是否有相同爸
return find(ocean, a) == find(ocean, b);
}
int getIdx(int x, int y) { // 唯一编号
return x * n + y;
}
}
- 时间复杂度:
- 空间复杂度:
C++
static const int N = 200 * 200 + 10;
class Solution {
int pac[N], atl[N];
int m, n;
int P, A;
vector<vector<int>> heights;
const int dirs[4][2] = {{-1,0}, {0,-1}, {1,0}, {0,1}};
public:
vector<vector<int>> pacificAtlantic(vector<vector<int>>& heights) {
this->heights = heights;
m = heights.size();
n = heights[0].size();
int tot = m * n;
P = tot + 1; // 流向pacific集合的爸
A = tot + 2; // 流向atlanti集合的爸
for(int i = 0; i <= A; i++)
pac[i] = atl[i] = i;
for(int i = 0; i < m; i++) {
for(int j = 0; j < n; j++) {
int idx = getIdx(i, j);
if(i == 0 || j == 0) // 上边界和左边界,与Pacific连通
if(!query(pac, P, idx))
DFS(pac, P, i, j);
if(i == m - 1 || j == n - 1) // 下边界和右边界,与Atlantic连通
if(!query(atl, A, idx))
DFS(atl, A, i, j);
}
}
vector<vector<int>> res;
for(int i = 0; i < m; i++) {
for(int j = 0; j < n; j++) {
int idx = getIdx(i, j);
if(query(pac, P, idx) && query(atl, A, idx)) { // 都能流到
vector<int> tmp;
tmp.emplace_back(i);
tmp.emplace_back(j);
res.emplace_back(tmp);
}
}
}
return res;
}
void DFS(int ocean[N], int uf, int x, int y) {
UNION(ocean, uf, getIdx(x, y));
for(auto d : dirs) { // 东南西北
int nx = x + d[0], ny = y + d[1];
if(nx < 0 || nx >= m || ny < 0 || ny >= n) // 超出边界
continue;
if(query(ocean, uf, getIdx(nx, ny)) || heights[nx][ny] < heights[x][y]) // 已遍历或低于当前
continue;
DFS(ocean, uf, nx, ny);
}
}
// 并查集
void UNION(int ocean[N], int a, int b) { // 并,认爸
ocean[find(ocean, a)] = ocean[find(ocean, b)];
}
int find(int ocean[N], int x) { // 查,找爸
if(ocean[x] != x)
ocean[x] = find(ocean, ocean[x]);
return ocean[x];
}
bool query(int ocean[N], int a, int b) { // 是否有相同爸
return find(ocean, a) == find(ocean, b);
}
int getIdx(int x, int y) { // 唯一编号
return x * n + y;
}
};
- 时间复杂度:
- 空间复杂度:
总结
来熟悉一下图的遍历,对遍历搜索代码结构还算熟悉,但套用并不熟练,小细节会丢。
然后学了一下并查集,找爸归祖宗的故事,实现起来简单但应用还是要理解一下的。
| 欢迎指正与讨论! |