376. Wiggle Subsequence
題目網址:https://leetcode.cn/problems/wiggle-subsequence/
題意:如果連續數字之間的差值在正數和負數之間交替, 則該 subsequence 稱為 Wiggle Subsequence。第一個差值(如果存在的話)可能是正數 or 負數。僅有一個元素 or 兩個不相等元素的 subsequence 也被視作 Wiggle Subsequence。
- e.g.
[1, 7, 4, 9, 2, 5]
是一個 Wiggle Subsequence, 因為差值(6, -3, 5, -7, 3)
是正負交替出現的。subsequence 可以通過從原 array 中刪除一些(也可以不刪除)元素來獲得, 剩下的元素保持其原先順序。
給一整數 array
nums
, 返回nums
中 最長 Wiggle Subsequence 的長度。進階:設計 $O(n)$ time 的演算法。
Solution 1:
想法:利用 DP
1. 定義狀態:
- 首先, 會在
nums
前先加上一個0
, 代表什麼元素都沒有的狀態up[i]
代表nums[1:i]
中以某一元素為結尾, 且「結尾上升」的最長 wiggle subsequence 之長度down[i]
代表nums[1:i]
中以某一元素為結尾, 且「結尾下降」的最長 wiggle subsequence 之長度2. 得到狀態轉移方程:
- 若
nums[i] > nums[i - 1]
:可以選出更長的結尾上升 wiggle subsequence, 但是結尾下降 wiggle subsequence 則無法選出更長的
up[i] = max(up[i - 1], down[i - 1] + 1)
down[i] = down[i - 1]
- 若
nums[i] < nums[i - 1]
:可以選出更長的結尾下降 wiggle subsequence, 但是結尾上升 wiggle subsequence 則無法選出更長的
up[i] = up[i - 1]
down[i] = max(down[i - 1], up[i - 1] + 1)
- 若
nums[i] == nums[i - 1]
:則結尾上升、下降 wiggle subsequence 都無法選出更長的
up[i] = up[i - 1]
down[i] = down[i - 1]
3. 初始化:
- 當沒有元素時,
up[0] = down[0] = 0
- 當只有一個元素時, 也會被視作為 wiggle subsequence, 故
up[1] = down[1] = 1
4. 你可能會問:
up[i]
和down[i]
只記錄nums[1:i]
中結尾上升、下降最長 wiggle subsequence 的長度, 又沒紀錄 subsequence 的結尾end
, 為什麼可以透過比較nums[i]
和nums[i - 1]
來決定狀態的轉移?不是應該比較nums[i]
和end
嗎?
當
nums[i] > nums[i - 1]
時:
假設
down[i - 1]
的結尾最大 idxj
為i - 1
, 則nums[j]
剛好遇到上升元素➔
up[i] = down[i - 1] + 1
假設
down[i - 1]
的結尾最大 idxj
小於i - 1
, 則nums[j] < nums[i - 1]
。因為如果nums[j] > nums[i - 1]
, 則nums[i - 1]
可替換nums[j]
成為結尾, 但實際上j < i - 1
, 故矛盾➔ 若
j < i - 1
, 則nums[j] < nums[i - 1]
且
nums[j:(i-1)]
必為遞增的。因為若非遞增, 則會產生波動, 並產生新的拐點k
使得down[j] < down[k]
, 這與之前假設的down[i - 1]
的結尾最大 idxj
矛盾➔
down[j:(i-1)]
皆等於down[j]
, 因為nums[j:(i-1)]
必為遞增的➔ 當
j < i - 1
且nums[i] > nums[i - 1]
, 依然滿足up[i] = down[i - 1] + 1
nums[i] < nums[i - 1]
:同理當
nums[i] == nums[i - 1]
:新元素無法用於任何 subsequence, 故保持不變
class Solution { |
- time:$O(n)$ ➔ for loop
- space:$O(n)$ ➔
up
,down
Solution 2:
想法:改進 Solution 1, 由於
up[i]
,down[i]
只會用到i - 1
的狀態, 因此只須記住上一次的狀態即可, 根本不需要開到 $O(n)$ space。由於up
,down
記住了上一次的狀態, 所以原先在 Solution 1 的 for loop 中沒有更新的部分在 Solution 2 就不用寫出來, e.g.up[i] = up[i - 1]
,down[i] = down[i - 1]
, 只需要寫更新的部分即可
class Solution { |
- time:$O(n)$ ➔ for loop
- space:$O(1)$ ➔ 只需常數空間
Solution 3:
想法:利用 Greedy, 盡可能地加入拐點。用
diff
紀錄當前的斜率,prevDiff
紀錄上一次的斜率, 只要diff != prevDiff
就把當前的點加入。若diff
和prevDiff
是同個趨勢 ornums[i] == nums[i - 1]
, 則不加入
class Solution { |
- time:$O(n)$ ➔ for loop
- space:$O(1)$ ➔ 只需常數空間