Medium草稿★★★★★O(m * n * log(m * n)) 時間 · O(m * n) 空間
GraphDijkstraBinary SearchUnion-FindHeap
Patterns📐 二分答案🔗 並查集🛣️ 帶權最短路⛰️ 堆・Top-K🎯 二分搜尋🕸️ 圖遍歷・連通分量
尚未複習過

解法已隱藏 — 先讀題目敘述、自己想想看,再點上方按鈕揭曉。

1631Path with Minimum EffortAdvanced GraphsMediumAdvanced Graphs

給定一個 m x n 的高度矩陣 heights,從左上角走到右下角。路徑的「effort」定義為路徑中相鄰格子高度差的最大值。求最小 effort 的路徑。

Example:

Input: heights = [[1,2,2],[3,8,2],[5,3,5]] Output: 2

Intuition

TIP

最小化路徑上的最大邊權 — 用修改版的 Dijkstra,距離定義為路徑上的最大高度差。

Approaches

⭐ 1. Modified Dijkstra — O(m * n * log(m * n)) / O(m * n)
  • Idea: 用最小堆,每次取出 effort 最小的節點。effort = max(目前 effort, 相鄰高度差)
  • Time: O(m * n * log(m * n))
  • Space: O(m * n)
class Solution {
    fun minimumEffortPath(heights: Array<IntArray>): Int {
        val m = heights.size
        val n = heights[0].size
        val dist = Array(m) { IntArray(n) { Int.MAX_VALUE } }
        dist[0][0] = 0
        val pq = java.util.PriorityQueue<IntArray>(compareBy { it[0] })
        pq.add(intArrayOf(0, 0, 0)) // effort, row, col
        val dirs = arrayOf(intArrayOf(0, 1), intArrayOf(0, -1), intArrayOf(1, 0), intArrayOf(-1, 0))

        while (pq.isNotEmpty()) {
            val (effort, r, c) = pq.poll()
            if (r == m - 1 && c == n - 1) return effort
            if (effort > dist[r][c]) continue

            for ((dr, dc) in dirs) {
                val nr = r + dr
                val nc = c + dc
                if (nr in 0 until m && nc in 0 until n) {
                    val newEffort = maxOf(effort, Math.abs(heights[r][c] - heights[nr][nc]))
                    if (newEffort < dist[nr][nc]) {
                        dist[nr][nc] = newEffort
                        pq.add(intArrayOf(newEffort, nr, nc))
                    }
                }
            }
        }
        return 0
    }
}
2. Binary Search + BFS — O(m * n * log(maxHeight)) / O(m * n)
  • Idea: 二分搜尋答案 effort,用 BFS 驗證是否存在 effort 以內的路徑
  • Time: O(m * n * log(maxHeight))
  • Space: O(m * n)
class Solution {
    fun minimumEffortPath(heights: Array<IntArray>): Int {
        val m = heights.size
        val n = heights[0].size
        var lo = 0
        var hi = 1_000_000

        while (lo < hi) {
            val mid = (lo + hi) / 2
            if (canReach(heights, m, n, mid)) hi = mid
            else lo = mid + 1
        }
        return lo
    }

    private fun canReach(heights: Array<IntArray>, m: Int, n: Int, maxEffort: Int): Boolean {
        val visited = Array(m) { BooleanArray(n) }
        val queue = ArrayDeque<IntArray>()
        queue.add(intArrayOf(0, 0))
        visited[0][0] = true
        val dirs = arrayOf(intArrayOf(0, 1), intArrayOf(0, -1), intArrayOf(1, 0), intArrayOf(-1, 0))

        while (queue.isNotEmpty()) {
            val (r, c) = queue.removeFirst()
            if (r == m - 1 && c == n - 1) return true
            for ((dr, dc) in dirs) {
                val nr = r + dr
                val nc = c + dc
                if (nr in 0 until m && nc in 0 until n && !visited[nr][nc]) {
                    if (Math.abs(heights[r][c] - heights[nr][nc]) <= maxEffort) {
                        visited[nr][nc] = true
                        queue.add(intArrayOf(nr, nc))
                    }
                }
            }
        }
        return false
    }
}

🔑 Takeaways