太平洋大西洋:从海岸反向往高处爬
水只能流向高度更低或相等的格子。正向穷举每个格子太慢。从两片海边沿「邻格 ≥ 当前」反爬,两边都爬到的格子就是答案。
太平洋接上边和左边,大西洋接下边和右边。从各自边界出发 BFS/DFS,只走向高度 ≥ 当前格的未访问邻居。两份 reach 都为真的坐标加入答案。时间 O(H·W),空间 O(H·W)。
这是 LeetCode 417. Pacific Atlantic Water Flow。矩阵 heights[r][c] 是海拔。水可以流向上下左右、高度小于等于当前的格子。矩阵上边和左边接太平洋,下边和右边接大西洋。求哪些格子的水既能流到太平洋,又能流到大西洋。
主例是 5×5:第一行 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。演示先铺太平洋边界,再爬完太平洋可达,再铺大西洋,最后两份标记叠出 5 个格子。
第一直觉是对每个格子 DFS「能不能下山到某一侧海」。最坏每个格子走整张图,重复路径极多。缺的是:能流到海,等价于海沿不上坡的反方向爬到它。边界只有 O(H+W) 个起点,两遍搜索即可。
能流到海 = 海能反爬到此
正向约束是邻格高度 ≤ 当前才能流过去。反过来:从已经能到海的格子出发,邻格高度 ≥ 当前,才可能是「水从邻格流下来」的上游。边界格子本身就靠海,一定能到对应的洋。
缺的是一份「这格的水能到太平洋吗」的标记,和另一份大西洋标记。没有标记,同一条上坡路径会被很多起点重复走。有了标记,每个格子对每个洋最多入队一次。答案不是两片洋的并集,是交集:两份都为真。
从缺口写两遍 BFS。第一遍起点是第 0 行和第 0 列,得到 pacific。第二遍起点是最后一行和最后一列,得到 atlantic。扩展条件相同:未访问且 heights[邻] ≥ heights[当前]。四角同时属于两侧边界,两遍都会从它们出发,这是对的。
用手走主例。演示第一帧只点亮太平洋边界:顶行五个 true,左列五个 true,其余 false。第二帧沿不下降的反方向爬完,内陆 (1,1)(1,2)(1,3)、(2,1)(2,2)(2,3)、(3,0)(3,1) 等被点亮;右下较低的格子爬不上去,仍是 false。第三帧从底行和右列出发铺大西洋。第四帧两表相交:同时为真的是 (0,4)、(1,3)、(2,2)、(2,3)、(4,0),共 5 格。
高度相等可以流,反爬时 ≥ 不能写成 >,否则平台会断开。不要只从四个角出发——整条边都靠海。每格每侧最多访问一次,总时间线性于格子数。
反向一次,胜过正向 m·n 次「流到海」难在起点太多。「从海边爬上去」只有两条海岸线当起点。标记数组既是 visited,也是最终集合。
Go:BFS 从边界反向扩散
func pacificAtlantic(heights [][]int) [][]int {m, n := len(heights), len(heights[0])dirs := [][2]int{{0, 1}, {0, -1}, {1, 0}, {-1, 0}}flow := func(start func() [][2]int) [][]bool {reach := make([][]bool, m)for i := range reach { reach[i] = make([]bool, n) }q := start()for _, p := range q { reach[p[0]][p[1]] = true }for len(q) > 0 {cur := q[0]; q = q[1:]for _, d := range dirs {nr, nc := cur[0]+d[0], cur[1]+d[1]if nr < 0 || nr >= m || nc < 0 || nc >= n { continue }if reach[nr][nc] || heights[nr][nc] < heights[cur[0]][cur[1]] { continue }reach[nr][nc] = trueq = append(q, [2]int{nr, nc})}}return reach}pac := flow(func() [][2]int {s := [][2]int{}for i := 0; i < m; i++ { s = append(s, [2]int{i, 0}) }for j := 0; j < n; j++ { s = append(s, [2]int{0, j}) }return s})atl := flow(func() [][2]int {s := [][2]int{}for i := 0; i < m; i++ { s = append(s, [2]int{i, n - 1}) }for j := 0; j < n; j++ { s = append(s, [2]int{m - 1, j}) }return s})res := [][]int{}for i := 0; i < m; i++ {for j := 0; j < n; j++ {if pac[i][j] && atl[i][j] { res = append(res, []int{i, j}) }}}return res}
1邻格高度 < 当前就 continue:反爬不能往更低处走。等高等于平台能过。
2pacific 从左列加顶行起步,atlantic 从右列加底行起步。角上的格子两边都会进起点。
3两份 reach 都真才写入结果。演示叠出 5 个格子。
总结
从两片海岸反爬,交集即答案。主例演示标出 5 格。
- 正向从每个格子找海会重复走同一条下山路。反向只从边界走两遍。
- 扩展条件是邻格 ≥ 当前。写成严格大于,等高平台会断。
- 答案是交集不是并集。只到一个洋的格子不要输出。