這篇文章的特點就是方法都是老方法,但是使用在這個題目上就顯得更加的巧妙。刷的題多不是什麼值得誇讚的事情,希望大家都能夠把做過的題刷精,哪管他如何變化,看過去都是一道題,這纔是厲害的。
leetcode: 209
給定一個含有 n 個正整數的數組和一個正整數 s ,找出該數組中滿足其和 ≥ s的長度最小的連續子數組,並返回其長度。如果不存在符合條件的連續子數組,返回 0。示例:
輸入: s = 7, nums = [2,3,1,2,4,3]
輸出: 2
解釋: 子數組 [4,3] 是該條件下的長度最小的連續子數組。
這道題的暴力法大家很容易想到,那就是遍歷一個起始位置,遍歷一個終止位置,然後對起始位置到終止位置求和,看是否大於等於,然後從中找到跨度最小,也就是最小的位置。
複雜度的解法
但是,大家應該保有一種思維就是,我不是每次去找一個和,顯然這樣可以找到組,然後對每組獨立求和,這樣求和的複雜度是。
大家應該具有一種思維就是,我已經求出了一個到,那麼再往上加一個數,可以直接在原來的和的基礎上加一個數,這樣每一-1次從到求和的複雜度就轉變成求,也就是。總的複雜度就是。
到這裏,這個方法就很好理解了,這裏直接給出代碼。
python代碼
class Solution:
def minSubArrayLen(self, s: int, nums: List[int]) -> int:
for i in range(1, len(nums)):
if nums[i] >= s:return 1
nums[i] += nums[i-1]
nums += [0]
for gap in range(1, len(nums)):
# 這裏直接使用的是子串的長度,而不是子串的起始位置
for j in range(gap-1, len(nums)):
if nums[j]-nums[j-gap] >= s:
return gap
return 0
值得一提的是,這裏使用了一個小技巧,就是python下標裏的-1,因爲想要直接在原數組上進行操作,直接在原數組最後面補了一個0,用這個作爲下標-1的位置來統一處理i=0的時候計算和,需要使用的情況。
如果到這裏就結束了,你會發現你的代碼被95%的人超過了,那麼本文也就沒有意義了。
接下來的兩種方法,大家可以在看文章的時候,根據標題停頓,看看能否自己根據標題提示,找到更好的解法。
妙用二分查找
在上一種方法中,事先已經對數組求好和了,我們得到的新數組nums[j]
實際上就是從第0個位置開始,到當前位置,這個數組的和。顯然這個數組是遞增數組。
我們可以在這個遞增數組中快速找到恰好比s大的第一個數,自然是使用二分查找。這樣實際上就是解決了,從第0個位置開始,大於等於s的最短子數組。
同理,我們還需要解決從第一個位置開始的。。。但是方法都是一致的,二分查找就行了。
這裏又得用到小技巧,我們第一輪求得了新數組nums[j]
實際上就是從第0個位置開始,到當前位置,這個數組的和,如果我們要計算從第一個位置開始的最短子數組,自然是不用從第一個位置再求一次和的。
我們從第0個位置求得和,不過是每一個數都比第零個·位置的和大了nums[0]
,也就是第1一個數之前所有的數之和。所以我們這個時候,可以繼續上新的nums
,但是這次不是找s了,而是找s+nums[1-1]
,這裏的1可以換成任何一個i,都是同理。
python代碼
class Solution:
def minSubArrayLen(self, s: int, nums: List[int]) -> int:
# 創造二分查找的條件,然後對每一個數開頭的列表都執行二分查找
for i in range(1, len(nums)):
if nums[i] >= s:return 1
nums[i] += nums[i-1]
nums += [0]
res = len(nums)+1
for i in range(len(nums)-2):
start,end = i,len(nums)-2
target = s+nums[i-1]
while start <= end:
mid = (start+end) >> 1
if nums[mid]>=target and nums[mid-1] < target:
res = min(res, mid-i+1)
break
elif nums[mid]>=target:
end = mid
else:
start = mid+1
else:
break
return 0 if res==len(nums)+1 else res
代碼核心就是一個二分查找,找到第一個比s+nums[i-1]
大的數。每一次查找的複雜度是,因爲需要從n個位置開始查找,加上最開始求和的複雜度,最終的複雜度就是。
巧用雙指針
這些方法似乎都是用數組從到的和。好像雙指針很適合來做這個事情,但是怎麼使用,還是需要做一番思考。保證雙指針儘量只往一個方向走,那麼就可以降低複雜度。
這一種方法的關鍵就在於雙指針都是從小往大長。我們先固定住起始指針,然後增大結尾指針,直到我們找到了一個滿足的子串,然後我們需要記錄這個長度。
這個時候,我們想要得到更好的解,自然不能繼續增大長度,而死減少長度,不用說,自然是增加起始指針。增加到什麼時候,增加到子串的和比s小了。這個時候自然不能繼續增大起始指針了,因爲越增大和越小,不能找到解。
所以就這樣往復的增大結尾指針,和比s大了再增大起始指針,這樣往復。很像毛毛蟲向前蠕動。別叫雙指針了,叫毛毛蟲算法好了。
python代碼
class Solution:
def minSubArrayLen(self, s: int, nums: List[int]) -> int:
# 雙指針法
res = len(nums)+1
sn = j = 0
for i in range(len(nums)):
sn += nums[i]
while sn >= s:
res = min(res, i-j+1)
sn -= nums[j]
j+=1
return 0 if res==len(nums)+1 else res
這樣到這裏算是大功告成了。可以看到其實方法都是已有的方法,但是需要把問題轉換成已知的問題。這個轉換過程纔是刷題最應該思考和掌握的。
在這裏希望大家能夠在刷題中多學習思維,掌握一些基本套路是爲了構建更好的構架底層架構,轉換思維就像是在底層架構上構建的一套操作系統,所有的題目就是在操作系統上運行的用戶軟件。也希望大家能夠在日積月累中有所進步,穩定好底層架構,然後構建一套精妙的操作系統。