LeetCode 778困难高级图
在水位上升的泳池中游泳 图解题解
这道题到底在问什么
grid[i][j] 是格子高度。t 秒时水位为 t,高度 ≤ t 的格可游入。从 (0,0) 出发,求游到 (R-1,C-1) 的最早时刻。
- 输入
- 3×3 高度矩阵
- 输出
- 答案 = 7
最优解:一步一步想明白
- 3这就是 Dijkstra 的味道:把「路径代价 = 路上最大高度」当作距离,贪心地永远先扩展代价最小的格。
- 4起点 (0,0) 高度 0 放进堆。堆里永远是「下一步能踏上的边界格」,按高度排,最浅的排最前。
- 5弹出当前堆里最浅的格 (0,0) 高度 0(橘色)。它已确定到达,ans 更新为 max 前值与 0 = 0。接着把它四周还没确定的邻居放进堆。
- 6邻居 (1,0) 高度 1 入堆(浅橘)。它现在是候选边界,等以后轮到它是堆里最浅的格时再扩散。
- 7邻居 (0,1) 高度 2 入堆(浅橘)。它现在是候选边界,等以后轮到它是堆里最浅的格时再扩散。
- 8弹出当前堆里最浅的格 (1,0) 高度 1(橘色)。它已确定到达,ans 更新为 max 前值与 1 = 1。接着把它四周还没确定的邻居放进堆。
- 9邻居 (2,0) 高度 8 入堆(浅橘)。它现在是候选边界,等以后轮到它是堆里最浅的格时再扩散。
- 10邻居 (1,1) 高度 3 入堆(浅橘)。它现在是候选边界,等以后轮到它是堆里最浅的格时再扩散。
- 11弹出当前堆里最浅的格 (0,1) 高度 2(橘色)。它已确定到达,ans 更新为 max 前值与 2 = 2。接着把它四周还没确定的邻居放进堆。
- 12邻居 (0,2) 高度 5 入堆(浅橘)。它现在是候选边界,等以后轮到它是堆里最浅的格时再扩散。
- 13弹出当前堆里最浅的格 (1,1) 高度 3(橘色)。它已确定到达,ans 更新为 max 前值与 3 = 3。接着把它四周还没确定的邻居放进堆。
- 14邻居 (2,1) 高度 6 入堆(浅橘)。它现在是候选边界,等以后轮到它是堆里最浅的格时再扩散。
- 15邻居 (1,2) 高度 4 入堆(浅橘)。它现在是候选边界,等以后轮到它是堆里最浅的格时再扩散。
- 16弹出当前堆里最浅的格 (1,2) 高度 4(橘色)。它已确定到达,ans 更新为 max 前值与 4 = 4。接着把它四周还没确定的邻居放进堆。
- 17邻居 (2,2) 高度 7 入堆(浅橘)。它现在是候选边界,等以后轮到它是堆里最浅的格时再扩散。
- 18弹出当前堆里最浅的格 (0,2) 高度 5(橘色)。它已确定到达,ans 更新为 max 前值与 5 = 5。接着把它四周还没确定的邻居放进堆。
- 19弹出当前堆里最浅的格 (2,1) 高度 6(橘色)。它已确定到达,ans 更新为 max 前值与 6 = 6。接着把它四周还没确定的邻居放进堆。
- 20弹出终点 (2,2) 高度 7!一路最大高度 ans = 7,这就是答案:第一次摸到右下角时的 ans 一定最优。
- 21到达右下角,最早时刻 = 7。蓝灰是探索过的格。现在沿父指针回溯,把真正走的那条路逐格点亮。
- 22路径点亮 (0,0) 高度 0(绿色)。整条路上最高的一格就是 7,水位涨到 7 时这条路全程可游。
- 23路径点亮 (1,0) 高度 1(绿色)。整条路上最高的一格就是 7,水位涨到 7 时这条路全程可游。
- 24路径点亮 (1,1) 高度 3(绿色)。整条路上最高的一格就是 7,水位涨到 7 时这条路全程可游。
- 25路径点亮 (1,2) 高度 4(绿色)。整条路上最高的一格就是 7,水位涨到 7 时这条路全程可游。
- 26路径点亮 (2,2) 高度 7(绿色)。整条路上最高的一格就是 7,水位涨到 7 时这条路全程可游。
⚠️ 容易写错的地方
✗ 错:把代价当成路径高度之和
✓ 对:代价是路上的最大高度
水位卡的是最高那格,不是累加
✗ 错:用普通队列/BFS 按层扩散
✓ 对:必须用最小堆按高度取
要永远先趟最浅的水,普通队列做不到贪心最优
✗ 错:弹出时忘了更新 ans
✓ 对:每次弹出都 ans=max(ans,h)
答案就是这一路弹出过的最大高度
完整代码(Python / C++ / Java)
Python
import heapq
def swimInWater(grid):
n = len(grid)
seen = {(0, 0)}
pq = [(grid[0][0], 0, 0)] # (高度, i, j)
ans = 0
while pq:
h, i, j = heapq.heappop(pq)
ans = max(ans, h)
if i == n - 1 and j == n - 1:
return ans
for di, dj in ((1,0),(-1,0),(0,1),(0,-1)):
ni, nj = i + di, j + dj
if 0 <= ni < n and 0 <= nj < n \
and (ni, nj) not in seen:
seen.add((ni, nj))
heapq.heappush(pq, (grid[ni][nj], ni, nj))
return ansC++
class Solution {
public:
int swimInWater(vector<vector<int>>& grid) {
int n = grid.size();
priority_queue<array<int,3>,
vector<array<int,3>>, greater<>> pq;
vector<vector<bool>> seen(n, vector<bool>(n));
pq.push({grid[0][0], 0, 0});
seen[0][0] = true;
int ans = 0, d[5] = {1, 0, -1, 0, 1};
while (!pq.empty()) {
auto [h, i, j] = pq.top(); pq.pop();
ans = max(ans, h);
if (i == n - 1 && j == n - 1) return ans;
for (int k = 0; k < 4; k++) {
int ni = i + d[k], nj = j + d[k + 1];
if (ni >= 0 && ni < n && nj >= 0 && nj < n
&& !seen[ni][nj]) {
seen[ni][nj] = true;
pq.push({grid[ni][nj], ni, nj});
}
}
}
return ans;
}
};Java
class Solution {
public int swimInWater(int[][] grid) {
int n = grid.length;
PriorityQueue<int[]> pq = new PriorityQueue<>(
(a, b) -> a[0] - b[0]); // 按高度小顶堆
boolean[][] seen = new boolean[n][n];
pq.offer(new int[]{grid[0][0], 0, 0});
seen[0][0] = true;
int ans = 0;
int[] d = {1, 0, -1, 0, 1};
while (!pq.isEmpty()) {
int[] c = pq.poll();
int h = c[0], i = c[1], j = c[2];
ans = Math.max(ans, h);
if (i == n - 1 && j == n - 1) return ans;
for (int k = 0; k < 4; k++) {
int ni = i + d[k], nj = j + d[k + 1];
if (ni >= 0 && ni < n && nj >= 0 && nj < n
&& !seen[ni][nj]) {
seen[ni][nj] = true;
pq.offer(new int[]{grid[ni][nj], ni, nj});
}
}
}
return ans;
}
}复杂度
时间
O(N²·logN)
每格进堆一次,堆操作 logN(N² 个格)
空间
O(N²)
堆 + 访问标记矩阵
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 在水位上升的泳池中游泳 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
为什么第一次弹出终点时 ans 就是最优,不用再找?+
Dijkstra 性质:堆按代价单调弹出,第一次确定终点时它的代价(路上最大高度)已是全局最小,后面不可能更小。
除了优先队列还有别的解法吗?+
有。二分答案 + BFS:二分水位 t,检查只用 ≤ t 的格能否连通起点终点;或并查集按高度从小到大加边,直到起终点连通。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄把 在水位上升的泳池中游泳 一步步讲透(全站已上线 905 份讲透,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。