LeetCode Biweekly Contest #Find the Power of K-Size Subarrays - 打怪開始
1 題目描述
這是我的第一次參加 LeetCode Contest,這次的題目是一道簡單的滑動窗口問題,給定一個數組nums
和一個整數k
,你需要針對大小為 k 的所有子陣列計算它們的 “power”:
- 如果子陣列的所有元素是連續且按升序排列,則該子陣列的 power 是其最大元素。
- 如果子陣列的元素不符合條件,則返回 -1。
2 解法
2.1 我的解法
我一開始的想法很簡單,我需要有一個function負責檢查是否為連續且按升序排列,如果是就返回最後一個數值,如果不是就返回-1。
1 | def helper(sub_nums: List[int]) -> int: |
接下來我們只要根據 k 的長度,把數組分成大小為 k 的子陣列,然後檢查每個子陣列是否符合條件,如果符合就找出最大值,如果不符合就返回-1。
1 | def resultsArray(self, nums: List[int], k: int) -> List[int]: |
但是以上的時間複雜度是 O(N*K),有沒有辦法可以得到時間複雜度是 O(N) 呢?
2.2 進階解法
他有個很重要的概念,什麼樣的狀況可以是可以紀錄當前的值,而非 -1 呢?
所謂的合格是,兩個值,要連續且按升序排列
- 當前的值,與前一個值相差 1(合格)
- 並且,過去從
i - k
開始,往後的數值到i
為止,都是連續且按升序排列
當時我看到有個人的解法是,他用了一個變數bad
來記錄,從上面的條件來看,如果當前的值不是連續升序時,這個影響必須後面連續 k 個值,只有在被影響的要素慢慢消除時,才可以反回當前的值(也就是最大值),從這裡可以看到就有點像是一個累積毒素的感覺。
- 當檢查到當下的值不符合條件時,我們就要把
bad
的值+1
,這就身體沈澱的毒素。 - 那我們什麼時候變得健康呢?那就是當我們的毒素都清理乾淨後
bad==0
。- 我們要確保前面從
i - k
開始到i
的都沒有毒素,如果遇到毒素 - 其實只要
bad
不要增加持續一段時間即可,也就是說當遇到毒素時,bad+=1
當遇到健康的時候bad-=1
- 我們要確保前面從
bad
可能會有2種狀態bad > 0
:代表我們有毒素,因此我們要回傳-1
bad == 0
:代表我們健康,我們要紀錄當前的值nums[i]
1 | def resultsArray3(self, nums: List[int], k: int) -> List[int]: |
上面的邏輯可能會通過題目的三個測試,但是如果我們碰到[5, 5, 5, 5, 5, 1, 2, 3, 4]
就是前面簡直就是垃圾人生,吃一堆毒素…,導致後面儘管很健康,但是也清理不掉毒素,可是這不是我們要的結果!我們希望,只要確保前k
個值沒有毒素就好。
從上圖來看,我們希望i=7
時,bad
是歸零的狀態因為[1, 2, 3]
是合法的,當bad==0
時我們才可以把nums[7]
塞到res
裡面。這意味著,為了確保遍歷到i=7
時bad
是歸零的,也就是說我們上面的程式碼要對bad+=1
的時機做調整。
你會看到上圖中,我們共畫了5個箭頭,我們要知道只要確保前k
個值沒有毒素就好,也就是說如果毒素離i
太遠,就會自動代謝掉,就是要毒素自動-1
。
- 從
i=3
時bad
已經累積了3(1+1+1)
個毒素- 我們往前看距離
k
的位置i=1
時,發現當時毒素有+1
- 但是其實
i=1
已經距離i=4
太遠了!這個毒素的影響性應該要代謝掉 - 我們只關心前
k
個值是否有毒素,所以我們這時候應該-1
把這個太過於遙遠的毒素代謝掉 - 最後
bad
變成(1+1+1)-1=2
- 我們往前看距離
- 從
i=4
時bad
此時是3(2+1)
- 因為上一個殘留的毒素是2,加上當前
i=4
是不健康的所以+1
- 我們往前看距離
k
的位置i=2
時,發現當時毒素有+1
- 但是其實
i=2
已經距離i=5
太遠了!這個毒素的影響性應該要被代謝掉了 - 我們只關心前
k
個值是否有毒素,所以我們這時候應該-1
,把這個太過於遙遠的毒素代謝掉 - 最後
bad
變成(2+1)-1=2
- 因為上一個殘留的毒素是2,加上當前
- 從
i=5
時bad
此時仍是3(2+1)
- 因為上一個殘留的毒素是2,加上當前
i=5
是不健康的所以+1
- 我們往前看距離
k
的位置i=3
時,發現當時毒素有+1
,但是其實i=3
已經距離i=6
太遠了!這個毒素的影響性應該要代謝掉 - 我們只關心前
k
個值是否有毒素,所以我們這時候應該-1
,把這個太過於遙遠的毒素代謝掉 - 最後
bad
變成(2+1)-1=2
- 因為上一個殘留的毒素是2,加上當前
- 從
i=6
時bad
此時是2(2+0)
- 因為上一個殘留的毒素是2,加上當前
i=6
是健康的所以+0
- 我們往前看距離
k
的位置i=4
時,發現當時毒素有+1
,但是其實i=4
已經距離i=6
太遠了!這個毒素的影響性應該要代謝掉 - 我們只關心前
k
個值是否有毒素,所以我們這時候應該-1
,把這個太過於遙遠的毒素代謝掉 - 最後
bad
變成(2)-1=1
- 因為上一個殘留的毒素是2,加上當前
- 從
i=7
時bad
此時是1(1+0)
- 因為上一個殘留的毒素是1,加上當前
i=7
是健康的所以+0
- 我們往前看距離
k
的位置i=5
時,發現當時毒素有+1
,但是其實i=5
已經距離i=7
太遠了!這個毒素的影響性應該要代謝掉 - 我們只關心前
k
個值是否有毒素,所以我們這時候應該-1
,把這個太過於遙遠的毒素代謝掉變成(1)-1=0
- 這時候我們
bad==0
就可以把nums[i]
塞到res
裡面。
- 因為上一個殘留的毒素是1,加上當前
你會發現一個點,bad+=1
的時機是:
往回看nums[i-k+1]
這個值,是否為不健康,但是因為已經過太久了,應該被代謝掉
怎麼知道不健康?也就是說nums[i-k+1]
與nums[i-k]
在比較時否為連續升序?
bad+=1的時機可以寫成這樣(要注意範圍不要變成負的 i>=k
)
1 | # i-k+1 看最久遠的病毒,如果是不健康,但是已經過太久,應該代謝掉(-1) |
那我們重新調整一下程式
1 | def resultsArray(self, nums: List[int], k: int) -> List[int]: |
還可以更精簡喔,下面是世界選手最簡潔的程式碼:
我們是否可以利用False的特性,直接加在bad上,這樣就不用判斷了!
- True: 代表 0
- False: 代表 1
1 | def resultsArray(self, nums: List[int], k: int) -> List[int]: |
3 結語
這個題目的解法是我看到別人排名很前面的人寫出來的,因為我還沒刷過sliding window的題目,所以當時完全沒想到這個解法,這次的比賽讓我學到了很多,希望下次可以更好。