Description
On an N x N grid, each square grid[i][j] represents the elevation at that point (i,j).
Now rain starts to fall. At time t, the depth of the water everywhere is t. You can swim from a square to another 4-directionally adjacent square if and only if the elevation of both squares individually are at most t. You can swim infinite distance in zero time. Of course, you must stay within the boundaries of the grid during your swim.
You start at the top left square (0, 0). What is the least time until you can reach the bottom right square (N-1, N-1)?
Example 1:
Input: [[0,2],[1,3]]
Output: 3
Explanation:
At time 0, you are in grid location (0, 0).
You cannot go anywhere else because 4-directionally adjacent neighbors have a higher elevation than t = 0.
You cannot reach point (1, 1) until time 3.
When the depth of water is 3, we can swim anywhere inside the grid.
Example 2:
Input: [[0,1,2,3,4],[24,23,22,21,5],[12,13,14,15,16],[11,17,18,19,20],[10,9,8,7,6]]
Output: 16
Explanation:
0 1 2 3 4
24 23 22 21 5
12 13 14 15 16
11 17 18 19 20
10 9 8 7 6
The final route is marked in bold.
We need to wait until time 16 so that (0, 0) and (4, 4) are connected.
Note:
问题描述
给定N * N 的二维数组grid, grid[i][j]表示点(i, j)的高度
现在开始下雨。在时刻t, 每个地方的水的高度为t.当前仅当两个点的高度都小于等于t时, 你可以从一个点游到另一个点。你可以不费时间游无限远。当然, 在你游的过程中, 你必须在二维数组的范围内。
你从(0, 0)开始, 直到你游到(N - 1, N - 1), 你最少需要花多少时间?
问题分析
问题可以简化为两点, 一个是选择合适的高度, 二是根据这个高度进行联通性检查, 即左上角能否与右下角联通。选择合适的高度可以通过二分查找(注意题目中明确指出了高度的范围), 联通性检查可以通过DFS或者BFS
解法1(优先级队列+BFS)
class Solution {
public int swimInWater(int[][] grid) {
int n = grid.length;
//优先级队列中的元素为数组,数组有3个元素, 分别是x, y, 以及高度, 队列以高度排序
PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> a[2] - b[2]);
boolean[][] visited = new boolean[n][n];
int[][] dirs = {{0, -1}, {-1, 0}, {0, 1}, {1, 0}};
visited[0][0] = true;
pq.offer(new int[]{0, 0, grid[0][0]});
//BFS
while(!pq.isEmpty()){
int[] info = pq.poll();
int i = info[0], j = info[1], max = info[2];
for(int[] dir : dirs){
int newI = dir[0] + i, newJ = dir[1] + j;
if(newI < 0 || newI >= n || newJ < 0 || newJ >= n) continue;
if(!visited[newI][newJ]){
visited[newI][newJ] = true;
int newmax = Math.max(max, grid[newI][newJ]);
if(newI == n - 1 && newJ == n - 1) return newmax;
pq.offer(new int[]{newI, newJ, newmax});
}
}
}
return 0;
}
}
解法2(二分查找+BFS)
class Solution {
public int swimInWater(int[][] grid) {
int n = grid.length;
int left = grid[0][0], right = n * n - 1;
//二分查找
while(left < right){
int mid = left + (right - left) / 2;
if(!reachable(grid, mid)){
left = mid + 1;
}else{
right = mid;
}
}
return right;
}
//BFS
public boolean reachable(int[][] grid, int E){
Queue<int[]> queue = new LinkedList<>();
int n = grid.length;
boolean[][] visited = new boolean[n][n];
int[][] dirs = {{0, -1}, {-1, 0}, {0, 1}, {1, 0}};
queue.add(new int[]{0, 0});
visited[0][0] = true;
while(!queue.isEmpty()){
int[] node = queue.poll();
if(node[0] == n - 1 && node[1] == n - 1) return true;
for(int[] dir : dirs){
int newI = node[0] + dir[0], newJ = node[1] + dir[1];
if(newI >= 0 && newI < n && newJ >= 0 && newJ < n){
if(!visited[newI][newJ] && grid[newI][newJ] <= E){
visited[newI][newJ] = true;
queue.offer(new int[]{newI, newJ});
}
}
}
}
return false;
}
}
解法3(并查集)
/*
这个解法与其他解法本质上是一样的, 只是这里对于高度是递增, 联通性的检查是通过并查集, 因此效率会低一些
*/
class Solution {
class UF {
int[] id;
public UF(int N) {
id = new int[N];
for (int i = 0; i < N; i++) {
id[i] = i;
}
}
public int root(int i) {
while (i != id[i]) {
id[i] = id[id[i]];
i = id[i];
}
return i;
}
public boolean isConnected(int p, int q) {
return root(p)==root(q);
}
public void union(int p, int q) {
if (isConnected(p, q)) return;
id[root(p)] = root(q);
}
}
public int swimInWater(int[][] grid) {
int N = grid.length;
UF uf = new UF(N * N);
int time = 0;
//通过并查集进行联通性检查
while (!uf.isConnected(0, N * N - 1)) {
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
if (grid[i][j] > time) continue;
if (i < N-1 && grid[i + 1][j] <= time) uf.union(i * N + j, i * N + j + N);
if (j < N-1 && grid[i][j + 1] <= time) uf.union(i * N + j, i * N + j + 1);
}
}
//高度顺序递增
time++;
}
return time - 1;
}
}
解法4(二分查找+DFS)
class Solution{
private int[] dx = new int[]{-1, 0, 1, 0};
private int[] dy = new int[]{0, -1, 0, 1};
public int swimInWater(int[][] grid){
int n = grid.length;
int lo = grid[0][0];
int hi = n * n - 1;
//二分查找
while(lo < hi){
int mid = lo + (hi - lo) / 2;
//通过DFS来进行联通性检查
if(isValid(mid, grid)){
hi = mid;
}else{
lo = mid + 1;
}
}
return lo;
}
private boolean isValid(int depth, int[][] grid){
boolean[][] visited = new boolean[grid.length][grid.length];
return dfs(depth, grid, 0, 0, visited);
}
private boolean dfs(int depth, int[][] grid, int currX, int currY, boolean[][] visited){
int n = grid.length;
visited[currX][currY] = true;
for(int i = 0; i < 4; ++i){
int newX = currX + dx[i];
int newY = currY + dy[i];
if(newX >= 0 && newX < n && newY >= 0 && newY < n && !visited[newX][newY] && grid[newX][newY] <= depth){
if(newX == n - 1 && newY == n - 1) return true;
if(dfs(depth, grid, newX, newY, visited)) return true;
}
}
return false;
}
}