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:代表我們有毒素,因此我們要回傳-1bad == 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的題目,所以當時完全沒想到這個解法,這次的比賽讓我學到了很多,希望下次可以更好。