LeetCode動態規劃之二

467. 環繞字符串中的唯一子串

  • 思路是dp[i]代表着從0-i這一段中符合規則出現的子串數目。
  • j從i-1開始往前走,直到不滿足相鄰,可以提前break,我沒寫出來。
  • 如何判斷相鄰,我的辦法是p[i] - p[i-1] == 1 and p[j+1] - p[j] == 1, 並且還要保證(p[i] - [j] + 26) % 26 == i - j

下面這段代碼,在這種情況下失敗了, 比如 za....zab. 最後一個條件不滿足。所以樣例只對了一部分。

 

 

class Solution:
    def findSubstringInWraproundString(self, p: str) -> int:
        if len(p) == 1:
            return 1
        if len(p) == 0:
            return 0
        dp = [0] * len(p)
        dp[0] = 1
        seen = set()
        seen.add(p[0])
        for i in range(1, len(p)):
            dp[i] += dp[i-1]
            for j in range(i-1, -1, -1):
                if ord(p[i]) - ord(p[i-1]) in [1, -25] and ord(p[j+1]) - ord(p[j]) in [1, -25] and (ord(p[i]) - ord(p[j])+26)%26 == i-j and not p[j:i+1] in seen:
                    dp[i] += 1
                    seen.add(p[j:i+1])
            if not p[i] in seen:
                dp[i] += 1
            seen.add(p[i])
        return dp[-1]

還可以把dp看成二維的。dp[i][j]代表着i-j這段是不是連續子序列。最後返回sum(dp)即可。

當然,實際上dp可以爲一個數字來維護。

然後以上方向還是思考錯了。將dp[i]是做,以字母i爲結尾的最大子串長度。那麼dp可以用len爲26的字典實現。最後返回的是values的sum

class Solution:
    def findSubstringInWraproundString(self, p: str) -> int:
        if not p:
            return 0
        hashmap=[0]*26
        hashmap[ord(p[0])-ord("a")]=1
        prelen=1
        for i in range(1,len(p)):
            gap=ord(p[i])-ord(p[i-1])
            if gap==1 or gap==-25:
                prelen+=1
            else:
                prelen=1
            hashmap[ord(p[i])-ord("a")]=max(hashmap[ord(p[i])-ord("a")],prelen)
        return sum(hashmap)

作者:jasss
鏈接:https://leetcode-cn.com/problems/unique-substrings-in-wraparound-string/solution/python3-by-jasss-19/
來源:力扣(LeetCode)
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。

474. 1和0(揹包問題)

題目:現在,假設你分別支配着 m 個 0 和 n 個 1。另外,還有一個僅包含 0 和 1 字符串的數組。

你的任務是使用給定的 m 個 0 和 n 個 1 ,找到能拼出存在於數組中的字符串的最大數量。每個 0 和 1 至多被使用一次。

思路:設dp[i][m'][n']的含義是:前i個字符中,用m'個0和n'個1, 能拼出字符集合中的字符的最大數目。

遞歸方程,狀態就有三種,i,m,n,選擇有兩種,拼第i個字符還是不拼。

dp[i][m][n] = max(d[i-1][m-m1][n-n1] + 1, dp[i-1][m][n]) 。 m1和n1代表第i個字符中0和1的數目。

 

class Solution:
    def findMaxForm(self, strs: List[str], m: int, n: int) -> int:
        if len(strs) == 0:
            return 0
        l = len(strs)
        dp = [[[0]*(n+1) for _ in range(m+1)] for _ in range(l)]
        dp[0][0][0] = 0
        n0, n1 = self.count_zero_one(strs[0])
        for i in range(n0, m+1):
            for j in range(n1, n+1):
                dp[0][i][j] = 1
        for i in range(1, l):
            m1, n1 = self.count_zero_one(strs[i])
            for a in range(m+1):
                for b in range(n+1):
                    temp = 0
                    if a - m1 >=0 and b - n1 >= 0:
                        temp = dp[i-1][a-m1][b-n1] + 1 
                    # 從兩個選擇中選出最大的  
                    temp = max(temp, dp[i-1][a][b])
                    dp[i][a][b] = temp
        return dp[-1][-1][-1]

    def count_zero_one(self, s):
        m = s.count('0')
        n = s.count('1')
        return m, n

 

213. 打家劫舍||

這道題是在打家劫舍|的基礎上做的修改,改動內容是,房屋成爲一個圈。

思路,如果偷第一家,就不能偷最後一家,那麼實際範圍就是0- n-2; 如果不偷第一家,就可以偷最後一家,那麼實際範圍是1- n-1。那隻要用第一次的解法,在兩個範圍內都求一次,就完事了。

打家劫舍|的思路: 遞歸方程是  cur = max(previous_one, previous_two + cur_value)

 

class Solution:
    def rob(self, nums: List[int]) -> int:
        if not nums:
            return 0
        if len(nums)<=2:
            return max(nums)
        def cal(s):
            pre=cur=0
            for i in range(len(s)):
                temp=max(pre+s[i],cur)
                cur, pre= temp,cur
            return cur
        return max(cal(nums[:-1]), cal(nums[1:]))

 

221. 最大正方形

這道題是求一個二維矩陣裏面, 一個正方形區域全部爲1的最大面積

思路: 某個位置(i,j)是正方形的右下角的條件是:(i-1,j-1)是某個正方形的右下角 && (i-1,j)是1 && (i, j-1)是1。 所以遞歸方程爲

dp[i][j] = min(dp[i-1][j-1], dp[i-1, j] , dp[i][j-1]) +1

注意到遞歸方程僅用了第i-1行和第i行,根據dp問題的經典優化方式,我們可以用兩個數組代替整個矩陣。更近一步,其實只用一個數組就行了。

class Solution:
    def maximalSquare(self, matrix: List[List[str]]) -> int:
        if not matrix:
            return 0
        m,n=len(matrix),len(matrix[0])
        dp=[0]*(n+1)
        res=0
        for i in range(m):
            pre=0
            for j in range(1, n+1):
                temp=dp[j]
                if matrix[i][j-1]=='0':
                    dp[j]=0
                else:
                    dp[j]=min(dp[j-1],dp[j],pre)+1
                    res=max(dp[j],res)
                pre=temp
        return res**2

用pre記錄dp[i-1][j-1]的數值,那麼整個dp可以壓縮爲一個數組。temp是dp[i-1][j], 先取出來保存,因爲j的位置要被代替成dp[i][j]了。


322. 零錢兌換(揹包問題)

題目:給定一個總金額和不同面額的硬幣集合,求用硬幣組合成總金額的最少硬幣數目,硬幣可以複用。如果不能組合,則返回-1.

這道題和之前的279. 完全平方數有點類似,都是揹包問題的變種

思路:dp[i]代表了金額i能用這些硬幣組合的最小數目,如果記硬幣集合爲{2,4,6}. 則i是否可以被組合取決於 i-2, i-4 , i-6這些金額是否能被組合。遞歸方程爲 dp[i] = min(dp[i-2], dp[i-4], dp[i-6]) + 1

class Solution:
    def coinChange(self, coins: List[int], amount: int) -> int:
        dp = [float('inf')] * (amount + 1)
        dp[0] = 0
        for i in range(1, amount + 1):
            for coin in coins:
                if i - coin <0:
                    continue
                dp[i] = min(dp[i], dp[i - coin] + 1)
        return dp[-1] if dp[-1] !=float('inf') else -1

而279.完全平方數的解法,求一個數字n能被完全平方數組合的最小個數。我們需要從i出發,i- cnt**2 +1來求, cnt逐漸增加1.

 

338. 比特位計數

題目:給定一個非負整數 num。對於 0 ≤ i ≤ num 範圍中的每個數字 ,計算其二進制數中的 1 的數目並將它們作爲數組返回。

思路: 暴力法的複雜度是O(n*size(int))。如果用DP解,dp理應是一個一維數組,那進而設dp[i]的含義是,數組i的二進制表示中1的數目。那dp[i]和之前的dp[k],k {0,...,i-1}有什麼聯繫呢。一個數字A左移1位得到B,最低位消失,最高位補0。那麼A的1的個數應該爲B+A&1。

遞歸方程: dp[i] = dp[i // 2] + i&1.

class Solution:
    def countBits(self, num: int) -> List[int]:
        dp = [0] * (num + 1)
        for i in range(1, num + 1):
            dp[i] = dp[i // 2] + i%2
        return dp        

343. 整數拆分(劍指offer的剪繩子題目)

357. 計算各個位數不同的數字個數

題目:給定一個非負整數n, 計算各位數字都不同的數字x的個數, 0<=x<10^n

思路: 一共就0-9 10個數字 所以11位數以上的數字肯定是存在重複。只考慮10位及以下的數字。接下來考慮dp的含義。狀態有1個,就是位數,所以dp爲1維數組,dp[i]的含義就是i位數存在每一位不重複的數字數目。接下來看有什麼選擇,這道題只有一種選擇,那就尋找dp[i]和之前的關係。初始狀態dp[0]=1 dp[1] = 9.  對於n爲2的情況,1-9這9個數字分別有其他9個數字,可以加在後面組成一個兩位數,而且是不重複的。那麼遞歸方程可以推出: dp[i] = dp[i-1] * (10-i +1)。 返回sum(dp)

class Solution:
    def countNumbersWithUniqueDigits(self, n: int) -> int:
        if n == 0:
            return 1
        dp = [0] * (n + 1)
        dp[0] = 1
        dp[1] = 9
        for i in range(2, n + 1):
            dp[i] = dp[i-1] * (10 - (i - 1))
        return sum(dp)

 

413. 等差數列劃分

尋找數組中等差數列的最大個數

思路:設dp[i]爲,以i爲起點的等差數列的數目。 第一層循環倒着遍歷,第二層循環,從第一層的i爲起點 正向遍歷。返回sum(dp)。 如果A[i] 和A[j]之間組成等差,那就dp[i] +=1, 意味找到一個以i爲起點的等差數列。如果不是,直接break跳出第二層循環。

class Solution:
    def numberOfArithmeticSlices(self, A: List[int]) -> int:
        m = len(A)
        dp = [0] * m
        if m <3:
            return 0
        for i in range(m-3, -1, -1):
            for j in range(i, m):
                if j - i < 2:
                    continue
                if A[i] - A[i+1] == A[j-1] - A[j] and A[j]-A[i] ==(j - i) * (A[i+1] - A[i]):
                    dp[i] += 1
                else:
                    break
        return sum(dp)

486. 預測贏家

一個數組代表分數,玩家1和2, 玩家1先手,只能選擇拿開頭和末尾的數組,如果一方拿了開頭,另一方只能拿末尾。預測玩家1是否必贏。

思路: 對於玩家1來說有三種狀態,拿分的區間, 從左端拿還是右端拿。 dp[i][k][k]. k 爲0或者1. 然後需要兩個dp數組分別爲玩家1和2記錄中間結果。但實際上,我們只需一個數組dp就可以,這個數組dp爲dp[i][j][k],k也是0,1。但是意義不一樣了。i和j代表在區間[i,j]之間拿, 0是玩家1先手的分數,1是玩家2後手的分。dp的初始化是當區間內僅爲1個數字的情況。選擇有從左拿還是從右邊拿。

遞歸方程 玩家1的分數 dp[i][j][0] = max(nums[i] + dp[i+1][j][1], dp[nums[j]+dp[i][j-1] );

玩家2的分數 dp[i][j][1] = sum(nums[i:j+1]) - dp[i][j][0]

class Solution:
    def PredictTheWinner(self, nums: List[int]) -> bool:
        n = len(nums)
        dp = [[[0,0] for _ in range(n)] for _ in range(n)]
        for i in range(n):
            dp[i][i][0] = nums[i]
            dp[i][i][1] = 0
        for i in range(n-1, -1, -1):
            for j in range(i+1, n):
                dp[i][j][0] = max(dp[i+1][j][1]+nums[i], dp[i][j-1][1]+nums[j])
                dp[i][j][1] = sum(nums[i:j+1]) - dp[i][j][0]
        print(dp)
        if dp[0][-1][0] >= dp[0][-1][1]:
            return True
        else:
            return False

 

523. 連續的子數組和

題目:

給定一個包含非負數的數組和一個目標整數 k,編寫一個函數來判斷該數組是否含有連續的子數組,其大小至少爲 2,總和爲 k 的倍數,即總和爲 n*k,其中 n 也是一個整數。

思路:這道題有點求二維區域面積的意思。首先暴力法就不說了。優化的暴力法:可以用dp[i]代表以i爲末尾的前綴和。那i和j的和可以記作dp[j]-dp[i],不過複雜度是On2。一種On的思路是:利用哈希表,一次遍歷得到答案。假設遍歷到cur這個位置,其對應的前綴和是sum, 那麼往字典中insert({sum%k:cur}),如果字典中本身有sum%K這個映射,則比較cur和哈希表記錄的index的差。

之所以這樣有效的原因是:

  • 兩個不同的前綴和的餘數相等,意味着這兩個前綴和之差就是k的倍數
  • sum=sum%k對求解前綴和的餘數沒有影響,他只是讓sum和餘數之間的差去掉,但爲什麼這樣子做呢,有兩個好處

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章