Description
給定整數陣列
obstacles,對每個位置i,找出以obstacles[i]結尾的最長非遞減子序列長度(元素必須從索引 0 到 i 中選取且保持原順序)。
Example:
Input: obstacles = [1,2,3,2] Output: [1,2,3,3]
Intuition#
LIS 的非遞減版本 — 用 patience sorting + 二分搜尋,對每個位置算出以它結尾的最長非遞減子序列。
- 和 LIS 類似,但條件是「非遞減」(允許相等)
- 維護
tails陣列,用 upper bound 而非 lower bound(因為允許相等) - 每個元素插入位置就是以它結尾的最長非遞減子序列長度
Approaches#
1: DP O(n^2) (TLE)#
- 概念: 對每個位置掃描前面所有 <= 它的元素,取最長 + 1
- 時間複雜度:
O(n^2) - 空間複雜度:
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
}
}⭐2: Patience Sorting + Binary Search (Upper Bound)#
- 概念: 維護非遞減的
tails陣列,用 upper bound 二分搜尋確定插入位置 - 時間複雜度:
O(n log n) - 空間複雜度:
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#
- Pattern: LIS 變體(非遞減)— 二分搜尋用 upper bound 而非 lower bound
- 關鍵技巧: 嚴格遞增用 lower bound(第一個 >=),非遞減用 upper bound(第一個 >);每個元素的插入位置直接就是答案