2.算法-動(dòng)態(tài)規(guī)劃

LIS問題

連續(xù)子數(shù)組最大和

def maxSubArray(self, nums: List[int]) -> int:
        for i in range(1,len(nums)):
            nums[i] = max(nums[i-1]+nums[i],nums[i])
        return max(nums)

最長連續(xù)遞增子序列長度

def findLengthOfLCIS(self, nums: List[int]) -> int:
        if not nums:
            return 0
        dp = [1] * len(nums)
        for i in range(1,len(nums)):
            if nums[i] > nums[i-1]:
                dp[i] = dp[i-1] + 1
            else :
                dp[i] = 1
        return max(dp)

最長遞增子序列長度

解法一:DP
def lengthOfLIS(self, nums: List[int]) -> int:
        if(not nums):
            return 0
        dp = [1]*len(nums)
        for i in range(len(nums)):
            for j in range(i):
                if nums[i]>nums[j]:
                    dp[i]=max(dp[i],dp[j]+1)
        return max(dp)
[進(jìn)階] 解法二:貪心+二分

思路:數(shù)組d[len]表示長度為len的的子序列末尾最大值,遍歷數(shù)組時(shí)用二分查找得到插入d的位置
舉例:[0, 8, 4, 12, 2]

第一步插入 0,d = [0]
第二步插入 8,d = [0, 8]
第三步插入 4,d = [0, 4]
第四步插入 12,d = [0, 4, 12]
第五步插入 2,d = [0, 2, 12]
最終得到最大遞增子序列長度為 3

[再進(jìn)階] 輸出該序列

思路:要輸出序列,就要確定序列的最大值,然后從后向前遍歷數(shù)組
解法一知道以每個(gè)元素結(jié)尾的最長子序列長度
解法二知道固定長度子序列結(jié)尾的最小值
結(jié)合這兩個(gè)信息就可以還原最長子序列

def LIS(arr):
        if not arr: return None
        n = len(arr)
        d = [arr[0]] 
        dp = [1] * n # 記錄以元素 arr[i] 結(jié)尾的最長遞增子序列的長度
        length = 1
        for i in range(1,n): # 從1開始遍歷
            if arr[i] > d[-1]:
                d.append(arr[i]) 
                length += 1
                dp[i] = length
            else:
                # 使用bisect庫
                index = bisect.bisect(d,arr[i])
                dp[i] = index + 1 # 找到的是下標(biāo),長度需要再加1
                d[index] = arr[i] # 插入到對(duì)應(yīng)位置
                # 手動(dòng)實(shí)現(xiàn)二分
                l, r = 0, len(d)-1
                loc = -1
                while (l <= r):
                    mid = (l+r)//2
                    if (arr[i] <= d[mid]):
                        loc = mid
                        r = mid - 1
                    else:
                        l = mid + 1
                dp[i] = loc + 1
                d[loc] = arr[i] 

        ans = []
        max_val = d[-1] 
        count = len(d)
        idx  = arr.index(max_val)
        
        for i in range(idx,-1,-1):
            if not ans or (arr[i] < ans[-1] and dp[i] == count):
                ans.append(arr[i])
                count -= 1
        return ans[::-1]

回文問題

最長回文子串(長度)

def longestPalindrome(s) :
        N = len(s)
        if N == 1: 
            return s
        dp = [[False for _ in range(N)] for _ in range(N)]
        for i in range(N):
            dp[i][i] = True
        max_len,start = 1,0
        for j in range(1,N):  #因?yàn)楹竺嫱鈱觠-1所以j要從1開始
            for i in range(j): # i一定是小于j的,所以是循環(huán)到j(luò)
                if(s[i] == s[j]):
                    if(j-i<3): #為什么這里是3  i+1 和 j-1 的距離小于1 j-1-(i+1)<1其實(shí)也就是 i+1和j-1相等,是一個(gè)字符的時(shí)候,肯定滿足回文
                        dp[i][j]=True
                    else:
                        dp[i][j] = dp[i+1][j-1]
                if(dp[i][j]):
                    l = j - i + 1
                    if(l > max_len):
                        max_len = l 
                        start = i
        return s[start:start+max_len]

這道題要注意一個(gè)比較重要的點(diǎn),至少對(duì)我來說比較重要,就是在寫二重循環(huán)的時(shí)候,為什么是先遍歷j 然后再遍歷i , 我們看下我們的思路中的這個(gè)公式 dp[i][j] = dp[i+1][j-1] 要有了后面的值才能賦值給前面,從這個(gè)二維矩陣就可以看出,列是外層循環(huán),i是內(nèi)層循環(huán),這樣就不會(huì)出錯(cuò)了

最長回文子序列長度

def longestPalindromeSubseq(self, s: str) -> int:
        n=len(s)
        dp=[[0]*n for _ in range(n)]  #定義動(dòng)態(tài)規(guī)劃狀態(tài)轉(zhuǎn)移矩陣
        for i in range(n):  #   初始化對(duì)角線,單個(gè)字符子序列就是1
            dp[i][i]=1
        for i in range(n-1,-1,-1):  #從右下角開始往上遍歷 注意這里是n-1不是n
            for j in range(i+1,n):
                if s[i]==s[j]:   #當(dāng)兩個(gè)字符相等時(shí),直接子字符串加2
                    dp[i][j]= dp[i+1][j-1]+2  
                else:           #不相等時(shí),取某邊最長的字符
                    dp[i][j]=max(dp[i][j-1],dp[i+1][j])
        return dp[0][-1]   #返回右上角位置的狀態(tài)就是最長

221 最大正方形 字節(jié)

思路已經(jīng)有了,但是最后有的case沒有通過,還不知道為什么

def maximalSquare(self, matrix: List[List[str]]) -> int:
        if not matrix: return 0
        r,c = len(matrix),len(matrix[0])
        if r == 0 or c == 0 : return 0
        dp = [[int(matrix[i][j]) for j in range(c)] for i in range(r)]
        fr,fc = dp[0], [dp[i][0] for i in range(r)]
        l = max(max(fr),max(fc))
        for i in range(1,r):
            for j in range(1,c):         
                tmp = dp[i-1][j-1]
                if(matrix[i][j]=='1' and tmp>0):
                    flag =  True
                    index = 0 
                    while index < tmp:
                        if matrix[i-index-1][j] == '0' or matrix[i][j-index-1] == '0':
                            flag = False
                            break
                        index += 1
                    if flag:
                        dp[i][j] = dp[i-1][j-1] + 1
                    else:
                        dp[i][j] = dp[i-1][j-1]
                l = max(l,dp[i][j])
        return l*l

和正確答案對(duì)比一下, 如何巧妙地把邊界條件包含在循環(huán)中而不是單獨(dú)判斷

def maximalSquare(self, matrix: List[List[str]]) -> int:
        if len(matrix) == 0 or len(matrix[0]) == 0:
            return 0
        
        maxSide = 0
        rows, columns = len(matrix), len(matrix[0])
        dp = [[0] * columns for _ in range(rows)]
        for i in range(rows):
            for j in range(columns):
                if matrix[i][j] == '1':
                    if i == 0 or j == 0:
                        dp[i][j] = 1
                    else:
                        dp[i][j] = min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]) + 1
                    maxSide = max(maxSide, dp[i][j])
        
        maxSquare = maxSide * maxSide
        return maxSquare

1024. 視頻拼接

這道題其實(shí)DP的思路不是很好想, 感覺貪心更好想
用dp[i]表示覆蓋[0,i)需要的最小空間數(shù)

def videoStitching(self, clips: List[List[int]], T: int) -> int:
        l = len(clips)
        dp = [0] + [l+1]*T #0一定是0了,其他初始化一個(gè)不可能的大數(shù)len(clips)+1
        for i in range(1,T+1):
            for aj,bj in clips:
                if aj < i <= bj: #[0,i)的后面部分可以被覆蓋
                    #如果用這個(gè)部分覆蓋,數(shù)目為dp[aj]+1 不用就是dp[i] 取最小值
                    dp[i] = min(dp[i],dp[aj]+1)
        return -1 if dp[T]==l+1 else dp[T]

139. 單詞拆分

繼續(xù)動(dòng)態(tài)規(guī)劃

1. 72. 編輯距離

def minDistance(self, word1: str, word2: str) -> int:
        m = len(word1)
        n = len(word2)
        #這里注意初始化的二維數(shù)組的時(shí)候,哪個(gè)在里面,哪個(gè)在外面
        dp = [[0 for _ in range(n+1)] for _ in range(m+1)] 
        for i in range(m+1):
            dp[i][0] = i 
        for j in range(n+1):
            dp[0][j] = j
        for i in range(1,m+1):
            for j in range(1,n+1):
                if(word1[i-1]==word2[j-1]):
                    dp[i][j]=dp[i-1][j-1]
                else:
                    dp[i][j]=min(dp[i-1][j]+1,dp[i][j-1]+1,dp[i-1][j-1]+1)
        return dp[-1][-1]

2.198. 打家劫舍

def rob(self, nums: List[int]) -> int:
        N = len(nums)
        if not nums:
            return 0
       
        if N == 1 : return nums[0]
        if N == 2 : return max(nums[0],nums[1])

        dp = list(range(N))
        dp[0] = nums[0]
        dp[1] = max(nums[0],nums[1])
        for i in range(2,len(nums)):
            dp[i] = max(dp[i-2] + nums[i], dp[i-1])

        return dp[-1]

3.213. 打家劫舍 II

怎么能想到把這問題拆分成 只搶第一家 和 只搶最后一家 兩個(gè)子問題???

def rob(self, nums: List[int]) -> int:
        N = len(nums)
        if not nums : return 0 
        if N <= 2 : return max(nums)

        def helper(nums):
            if len(nums) <= 2 : return max(nums)
            dp = [0] * len(nums)
            dp[0] = nums[0]
            dp[1] = max(nums[0],nums[1])
            for i in range(2,len(nums)):
                dp[i]=max(dp[i-1],dp[i-2]+nums[i])
            return dp[-1]
            
        return max(helper(nums[1:]),helper(nums[:-1]))

2 72. 編輯距離

我看到“方法一”三個(gè)字的時(shí)候,驚喜地以為還有方法二。。沒有,這次真沒有。動(dòng)態(tài)規(guī)劃是個(gè)好東西,但難就難在如何定義DP數(shù)組里值的含義。聽我來給你捋一捋。

啥叫編輯距離?我們說word1和word2的編輯距離為X,意味著word1經(jīng)過X步,變成了word2,咋變的你不用管,反正知道就需要X步,并且這是個(gè)最少的步數(shù)。

我們有word1和word2,我們定義dp[i][j]的含義為:word1的前i個(gè)字符和word2的前j個(gè)字符的編輯距離。意思就是word1的前i個(gè)字符,變成word2的前j個(gè)字符,最少需要這么多步。

例如word1 = "horse", word2 = "ros",那么dp[3][2]=X就表示"hor"和“ro”的編輯距離,即把"hor"變成“ro”最少需要X步。

如果下標(biāo)為零則表示空串,比如:dp[0][2]就表示空串""和“ro”的編輯距離

定理一:如果其中一個(gè)字符串是空串,那么編輯距離是另一個(gè)字符串的長度。比如空串“”和“ro”的編輯距離是2(做兩次“插入”操作)。再比如"hor"和空串“”的編輯距離是3(做三次“刪除”操作)。

定理二:當(dāng)i>0,j>0時(shí)(即兩個(gè)串都不空時(shí))dp[i][j]=min(dp[i-1][j]+1,dp[i][j-1]+1,dp[i-1][j-1]+int(word1[i]!=word2[j]))。

啥意思呢?舉個(gè)例子,word1 = "abcde", word2 = "fgh",我們現(xiàn)在算這倆字符串的編輯距離,就是找從word1,最少多少步,能變成word2?那就有三種方式:

知道"abcd"變成"fgh"多少步(假設(shè)X步),那么從"abcde"到"fgh"就是"abcde"->"abcd"->"fgh"。(一次刪除,加X步,總共X+1步)
知道"abcde"變成“fg”多少步(假設(shè)Y步),那么從"abcde"到"fgh"就是"abcde"->"fg"->"fgh"。(先Y步,再一次添加,加X步,總共Y+1步)
知道"abcd"變成“fg”多少步(假設(shè)Z步),那么從"abcde"到"fgh"就是"abcde"->"fge"->"fgh"。(先不管最后一個(gè)字符,把前面的先變好,用了Z步,然后把最后一個(gè)字符給替換了。這里如果最后一個(gè)字符碰巧就一樣,那就不用替換,省了一步)
以上三種方式算出來選最少的,就是答案。所以我們?cè)倏纯炊ɡ矶?/p>

dp[i][j]=min(dp[i-1][j]+1,dp[i][j+1]+1,dp[i][j]+int(word1[i]!=word2[j]))
dp[i-1][j]:#情況一
dp[i][j-1]+1:#情況二
dp[i-1][j-1]+int(word1[i]!=word2[j]):#情況三

有了定理二的遞推公式,你就建立一個(gè)二維數(shù)組,考慮好空串的情況,總會(huì)寫出來


進(jìn)階


先把二維數(shù)組的方法做出來,要還沒做出來呢,先別往下看。

由定理二可知,dp[i][j]只和dp[i-1][j],dp[i][j-1],dp[i-1][j-1]三個(gè)量有關(guān),即二維數(shù)組中,當(dāng)前元素的左邊,上邊,左上角三個(gè)元素。

那我們不用這么大的二維數(shù)組存啊!我們就用一維數(shù)組,表示原來二維數(shù)組中的一行,然后我們就反復(fù)覆蓋里面的值。dp[i-1][j]就是我當(dāng)前左邊的元素,dp[i][j-1]是沒覆蓋前我這里的值,dp[i-1][j-1]好像找不見了?那我們就單獨(dú)用一個(gè)變量存著它,我們把它叫l(wèi)u(left up),則代碼為:

class Solution:
    def minDistance(self, word1: str, word2: str) -> int:
        m=len(word1)
        n=len(word2)
        dp=list(range(n+1))
        for i in range(m):
            lu=dp[0]
            dp[0]=i+1
            for j in range(n):
                dp[j+1],lu=min(dp[j]+1,dp[j+1]+1,lu+int(word1[i]!=word2[j])),dp[j+1]
        return dp[-1]

5. 16. 最接近的三數(shù)之和

有了之前3數(shù)之和的經(jīng)驗(yàn),這個(gè)還是解答起來比較簡單的

def threeSumClosest(self, nums: List[int], target: int) -> int:
        nums.sort()
        distance = pow(10,4)
        res = 0
        for i in range(len(nums)):
            low, high = i + 1, len(nums)-1
            while(low < high):
                sum_value = nums[i] + nums[low] + nums[high]
                if abs(sum_value-target) < distance:
                    distance = abs(sum_value-target)
                    res = sum_value
                if (sum_value > target):
                    high -= 1
                elif (sum_value == target):
                    return target
                else:
                    low += 1
        return res

6. 454. 四數(shù)相加 II

這題目用我大python有點(diǎn)牛逼了。。。也太簡潔明快了

def fourSumCount(self, A: List[int], B: List[int], C: List[int], D: List[int]) -> int:
        record = collections.Counter(a + b for a in A for b in B)
        return sum(record.get(- c - d, 0) for c in C for d in D)

4. 劍指 Offer 60. n個(gè)骰子的點(diǎn)數(shù)

這確定是個(gè)簡單題目么。。。?感覺還是有點(diǎn)復(fù)雜的,

從題解來看,刷到了第一個(gè)動(dòng)態(tài)規(guī)劃的題目。那么我們來理一下DP問題的思路吧
  • 首先要確定如何表示狀態(tài) 用dp[n][j]表示投擲完 n 枚骰子后,點(diǎn)數(shù) j 的出現(xiàn)次數(shù)
  • 然后找出狀態(tài)轉(zhuǎn)移方程,dp[n][j] = dp[n-1][j-1] + ... dp[n-1][j-6]
  • 最后確定邊界條件 投擲完 1 枚骰子后,點(diǎn)數(shù)從1到6各出現(xiàn)一次 dp[1][i]=1
def twoSum(self, n: int) -> List[float]:
        dp = [ [0 for _ in range(6*n+1)] for _ in range(n+1)] #如何初始化一個(gè)n行6n列的二維數(shù)組
        for i in range(1,7):
            dp[1][i] = 1
        for i in range(2,n+1):
            for j in range(i,i*6+1):
                for k in range(1,7):
                    if(j>k):
                        dp[i][j]+=dp[i-1][j-k]
        res = []
        for i in range(n,6*n+1):
            res.append(dp[n][i]*1.0/6**n)
        return res

反復(fù)品了一下,好像又沒有那么難,重點(diǎn)在于思路、套路

5. 劍指 Offer 49. 丑數(shù)

1003am-1023am
有事耽誤了幾天,看了下答案,好吧,表示自己真得想不出來 mark
三指針動(dòng)態(tài)規(guī)劃問題

def nthUglyNumber(self, n: int) -> int:
        dp, a, b, c = [1] * n, 0, 0, 0
        for i in range(1, n):
            n2, n3, n5 = dp[a] * 2, dp[b] * 3, dp[c] * 5
            dp[i] = min(n2, n3, n5)
            if dp[i] == n2: a += 1
            if dp[i] == n3: b += 1
            if dp[i] == n5: c += 1
        return dp[-1]
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,967評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,273評(píng)論 3 415
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,870評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,742評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,527評(píng)論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,010評(píng)論 1 322
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,108評(píng)論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,250評(píng)論 0 288
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,769評(píng)論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,656評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,853評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,371評(píng)論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,103評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,472評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,717評(píng)論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,487評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,815評(píng)論 2 372