Description
給定一個未排序的整數陣列
nums,回傳最長嚴格遞增子序列的個數。
Example:
Input: nums = [1,3,5,4,7] Output: 2
Intuition#
在 LIS 的 DP 基礎上,額外維護一個計數陣列來追蹤每個長度有多少種方式達成。
len[i]= 以nums[i]結尾的最長遞增子序列長度cnt[i]= 以nums[i]結尾的最長遞增子序列個數- 當找到更長的 LIS 時重置計數,長度相等時累加計數
Approaches#
1: DP O(n^2)#
- 概念: 對每個元素掃描前面所有比它小的元素,維護長度和計數
- 時間複雜度:
O(n^2) - 空間複雜度:
O(n)
class Solution {
fun findNumberOfLIS(nums: IntArray): Int {
val n = nums.size
val len = IntArray(n) { 1 }
val cnt = IntArray(n) { 1 }
var maxLen = 1
for (i in 1 until n) {
for (j in 0 until i) {
if (nums[j] < nums[i]) {
if (len[j] + 1 > len[i]) {
len[i] = len[j] + 1
cnt[i] = cnt[j]
} else if (len[j] + 1 == len[i]) {
cnt[i] += cnt[j]
}
}
}
maxLen = maxOf(maxLen, len[i])
}
var result = 0
for (i in 0 until n) {
if (len[i] == maxLen) result += cnt[i]
}
return result
}
}⭐2: Segment Tree (Optimal)#
- 概念: 用線段樹在值域上維護 (最長長度, 計數) 對,支援快速查詢和更新
- 時間複雜度:
O(n log n) - 空間複雜度:
O(n)
class Solution {
fun findNumberOfLIS(nums: IntArray): Int {
// 座標壓縮
val sorted = nums.toSortedSet().toList()
val rank = HashMap<Int, Int>()
sorted.forEachIndexed { i, v -> rank[v] = i + 1 }
val m = sorted.size
// 線段樹:每個節點存 (maxLen, count)
val treeLen = IntArray(4 * m)
val treeCnt = IntArray(4 * m)
fun merge(len1: Int, cnt1: Int, len2: Int, cnt2: Int): Pair<Int, Int> {
if (len1 > len2) return len1 to cnt1
if (len2 > len1) return len2 to cnt2
return len1 to (cnt1 + cnt2)
}
fun query(node: Int, s: Int, e: Int, l: Int, r: Int): Pair<Int, Int> {
if (r < s || e < l) return 0 to 0
if (l <= s && e <= r) return treeLen[node] to treeCnt[node]
val mid = (s + e) / 2
val left = query(2 * node, s, mid, l, r)
val right = query(2 * node + 1, mid + 1, e, l, r)
return merge(left.first, left.second, right.first, right.second)
}
fun update(node: Int, s: Int, e: Int, pos: Int, len: Int, cnt: Int) {
if (s == e) {
val (ml, mc) = merge(treeLen[node], treeCnt[node], len, cnt)
treeLen[node] = ml
treeCnt[node] = mc
return
}
val mid = (s + e) / 2
if (pos <= mid) update(2 * node, s, mid, pos, len, cnt)
else update(2 * node + 1, mid + 1, e, pos, len, cnt)
val (ml, mc) = merge(treeLen[2 * node], treeCnt[2 * node], treeLen[2 * node + 1], treeCnt[2 * node + 1])
treeLen[node] = ml
treeCnt[node] = mc
}
var maxLen = 0
var ans = 0
for (num in nums) {
val r = rank[num]!!
val (prevLen, prevCnt) = if (r > 1) query(1, 1, m, 1, r - 1) else 0 to 0
val curLen = prevLen + 1
val curCnt = if (prevCnt == 0) 1 else prevCnt
update(1, 1, m, r, curLen, curCnt)
if (curLen > maxLen) { maxLen = curLen; ans = curCnt }
else if (curLen == maxLen) ans += curCnt
}
return ans
}
}🔑 Takeaways#
- Pattern: LIS 計數 — 在標準 LIS DP 上增加計數維度
- 關鍵技巧: 當新的長度等於已知最長時,累加計數而非重置;O(n^2) 解法在面試中通常已足夠