Hard草稿★★★★★O(n log n) 時間 · O(n) 空間
Dynamic ProgrammingArrayBinary SearchGreedy
Patterns🎯 二分搜尋📈 線性 DP・Kadane🪙 貪心
尚未複習過

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

1964Find the Longest Valid Obstacle Course at Each Position1-D DPHard1-D DP

給定整數陣列 obstacles,對每個位置 i,找出以 obstacles[i] 結尾的最長非遞減子序列長度(元素必須從索引 0 到 i 中選取且保持原順序)。

Example:

Input: obstacles = [1,2,3,2] Output: [1,2,3,3]

Intuition

TIP

LIS 的非遞減版本 — 用 patience sorting + 二分搜尋,對每個位置算出以它結尾的最長非遞減子序列。

Approaches

1. Memoization (Top-Down LIS) — O(n^2) / O(n)
  • Idea: 定義 f(i) = 以 obstacles[i] 結尾的最長非遞減子序列長度,遞迴尋找前面所有 j 滿足 obstacles[j] <= obstacles[i] 的最大 f(j),加 memo
  • Time: O(n^2) - 每個狀態 O(n) 轉移,共 n 個狀態
  • Space: O(n) - memo + 遞迴堆疊
class Solution {
    fun longestObstacleCourseAtEachPosition(obstacles: IntArray): IntArray {
        val n = obstacles.size
        val memo = IntArray(n) { -1 }
        fun dp(i: Int): Int {
            if (memo[i] != -1) return memo[i]
            var best = 1
            for (j in 0 until i) {
                if (obstacles[j] <= obstacles[i]) {
                    best = maxOf(best, dp(j) + 1)
                }
            }
            memo[i] = best
            return best
        }
        return IntArray(n) { dp(it) }
    }
}
2. Bottom-Up DP O(n^2) (TLE) — O(n^2) / O(n)
  • Idea: 對每個位置掃描前面所有 <= 它的元素,取最長 + 1
  • Time: O(n^2)
  • Space: O(n)
class Solution {
    fun longestObstacleCourseAtEachPosition(obstacles: IntArray): IntArray {
        val n = obstacles.size
        val dp = IntArray(n) { 1 }
        for (i in 1 until n) {
            for (j in 0 until i) {
                if (obstacles[j] <= obstacles[i]) {
                    dp[i] = maxOf(dp[i], dp[j] + 1)
                }
            }
        }
        return dp
    }
}
⭐ 3. Patience Sorting + Binary Search (Upper Bound) — O(n log n) / O(n)
  • Idea: 維護非遞減的 tails 陣列,用 upper bound 二分搜尋確定插入位置
  • Time: O(n log n)
  • Space: O(n)
class Solution {
    fun longestObstacleCourseAtEachPosition(obstacles: IntArray): IntArray {
        val n = obstacles.size
        val ans = IntArray(n)
        val tails = mutableListOf<Int>()

        for (i in 0 until n) {
            val num = obstacles[i]
            // upper bound:找第一個 > num 的位置
            var lo = 0; var hi = tails.size
            while (lo < hi) {
                val mid = (lo + hi) / 2
                if (tails[mid] <= num) lo = mid + 1 else hi = mid
            }
            ans[i] = lo + 1
            if (lo == tails.size) tails.add(num) else tails[lo] = num
        }
        return ans
    }
}

🔑 Takeaways