Leetcode--DP

32. Longest Valid Parentheses

dp[i] = dp[start - 1] + (i - start + 1): dp[i]表示到第i個位置為止的有效括號長度,dp[start - 1]表示在start之前的有效括號長度,i - start + 1表示從i到start中間的有效括號長度,start是從棧里pop出來與右括號相對應的左括號,i是當前右括號的下標
if s[i] == '(': stack.append(i),注意存放的是左括號下標
else: if stack: start = stack.pop() 如果遇到了右括號,且棧不為空,就從stack里pop出一個左括號的下標作為start
注意最后返回的是max(dp)

53. Maximum Subarray

假設我們已知第i步的global[i](全局最優)和local[i](局部最優),那么第i+1步的表達式是:
local[i+1]=Math.max(A[i], local[i]+A[i]),就是局部最優是一定要包含當前元素,所以不然就是上一步的局部最優local[i]+當前元素A[i](因為local[i]一定包含第i個元素,所以不違反條件),但是如果local[i]是負的,那么加上他就不如不需要的,所以不然就是直接用A[i];
global[i+1]=Math(local[i+1],global[i]),有了當前一步的局部最優,那么全局最優就是當前的局部最優或者還是原來的全局最優(所有情況都會被涵蓋進來,因為最優的解如果不包含當前元素,那么前面會被維護在全局最優里面,如果包含當前元素,那么就是這個局部最優)

70. Climbing Stairs

這道題目是求跑樓梯的可行解法數量。每一步可以爬一格或者兩個樓梯,可以發現,遞推式是f(n)=f(n-1)+f(n-2),也就是等于前一格的可行數量加上前兩格的可行數量。熟悉的朋友可能發現了,這個遞歸式正是[斐波那契數列]

注意當n==0時,也返回1. 初始化時,dp[0]=1, dp[1] = 2, 是從0和1開始,不是從1和2開始。
follow up是有沒有更好的解法: 有,O(logn),使用斐波那契的通項公式

general term formula
Follow up實現

91. Decode Ways

dp[i]存儲的是加入s中第i-1個元素后有多少種decode方式,所以dp[]初始化時的長度要比s多一位,并且使dp[0]=1

  • 注意,0只能出現在1或者2后邊,構成10,20,如果單獨出現的0,則無法解析
  • 如果字符s[i-1]不為0,則這個字符可以獨立地被解析,例如:1-9,dp[i] += dp[i-1]
  • 如果字符s[i-2:i]大于'09'且小于'27',證明i-1位字符和i-2位字符可以放在一起被解析,dp[i] += dp[i-2]
  • dp[i-2:i] < '27'這是按ASCII碼值來比較的,python中可以對字符串進行比較,都是先將其轉換為ASCII碼值。其中,大寫字母的值都小于小寫字母,同時('ab'<'abc')即空位的序數為所有字符最小
  • 最后返回dp[-1]

96. Unique Binary Search Trees

這道題在樹的分類里已經寫過了,但還是有些細節需要添加。

  • res數組要初始化n+1位,因為包括0在內了,并且res[0]和res[1]都要初始化為1,其余均為0
  • res[i]+= res[j]*res[i-j-1], res[j]代表i左邊的left branch數量,res[i-j-1]代表right branch數量。j是i左邊的node個數,i-j-1是i右邊的node個數,因為n是順序分解的,所以個數相同,生成的BST就相同。
  • 注意外層for循環i的范圍:for i in range(2, n+1):
  • 最后返回res[n]

95. Unique Binary Search Trees II

雖然在dp分類里,但其實更偏向于divide and conquer

  • 構建一個生產子樹的函數,參數為start, end,分別等于1和n
  • 在這個函數內,如果start>end,就返回[None], 括號里一定要有none, 如果start==end, 就返回[TreeNode(start)]
  • for i in range(start, end+1), end要加1
  • leftsubtree = self.generate(start, i-1), rightsubtree = self.generate(i+1, end)
  • 在for循環前要初始化res = []
  • 將左右子樹用兩個for循環排列組合起來,要先root = TreeNode(i), 每組合成功一個后就res.append(root)

120. Triangle

這道題之前也寫過了,需要注意的點有:

  • res初始化時的長度為triangle最后一行的長度再加1,加1是因為如果triangle里只有一行,那就超出了遞歸式里的索引范圍
  • 思路是將triangle reverse,然后針對每一行(倒序)里的每一個數字,更新res[i],等于當前數字,加上一組的min(res[i], res[i+1])
  • 最后返回res[0], 因為第一行只有一個數字

121. Best Time to Buy and Sell Stock

又是局部最優,全局最優方法。初始化local和res均為0, 先判斷數組是不是遞減的,如果是就返回0if sorted(prices) == prices[::-1]: return 0
若不是遞減的,針對數組中每一個數字,計算
local = max(0, local + prices[i+1] - prices[i]), res = max(res, local)

  • i的循環范圍是for i in range(len(prices)-1), 因為后邊有i+1
  • 更新local時,應該加上原來的Local, 和0比較是因為價格有可能是負數。
  • 為什么要加上原來的Local, 因為1,5,3,6, 1到6的距離是5,等于(5-1)+(3-5)+(6-3)

139. Word Break

針對動態規劃的思路:

  1. 決定存儲什么歷史信息以及用什么數據結構來存儲
  2. 遞推式,就是如何從存儲的歷史信息中得到當前結果
  3. 初始值的設定
    這道題,dp[i]表示的是在s中的前i-1個字符s[:i]是否存在wordDict里,初始化dp的長度要是s的長度加1,因為要存儲dp[0]=True
    雙重for循環,注意內層循環時j的范圍是for j in range(i, len(s)):, 從i開始到字符串結束。
    判斷條件if dp[i] and s[i:j+1] in wordDict: dp[j+1] = True
    dp[i]為真證明s的前i-1個字符存在里邊,如果s[i:j+1]也存在,我們就更新dp[j+1]為真
    最后返回dp[-1],d[7] is True because there is "code" in the dictionary that ends at the 7th index of "leetcode" AND d[3] is True

152. Maximum Product Subarray

和maximum sum subarray類似,只不過這道題是求最大乘積。方法也是先初始化maxlocal和res, 但這道題要再加一個minlocal,因為兩個很小的負數相乘之后可能會變成很大的整數。所以對nums進行遍歷時,遞推式里要再加一項對minlocal的更新。
并且要注意因為已經初始化了maxlocal, minlocal, res = nums[0], nums[0], nums[0], 所以for循環要從1開始。
在每次循環過程中:

maxcopy = maxlocal
maxlocal = max(maxlocal*nums[i], nums[i], minlocal*nums[i])
minlocal = min(maxcopy*nums[i], nums[i], minlocal*nums[i])
res = max(maxlocal, res)

要先將maxlocal復制一下,作為計算minloca時使用

62. Unique Paths

具體思路參考第161頁https://www.dropbox.com/s/d839bnp9lcroxmq/Sample_Interview_Questions_Set_16.pdf?dl=0

  • 需要注意的是,我們可以用二維數組代替一維數組。只需要對一維數組更新m次就可以。因為在二維數組中,每個方格所擁有的路徑數等于上邊方格?左邊方格。在一維數組中,每次更新時,當前方格就屬于上邊方格,所以只需要加上左邊方格就可。
  • 還有一點需要注意,內部循環從1開始. 這是因為第一列中每個方格的路徑數始終都是1
for i in range(m): 
       for j in range(1, n)```
- 初始化res[0] = 1,res的長度要和n一樣!!
#63. Unique Paths II
在上一題的基礎上增加了障礙,即有的方格為1時便不可通過。總體思想沒有變化,依然可以用一維數組代替二維數組。
- 這次內部循環不能從1開始,因為第一列的方格中有可能存在障礙,即值為1,所以每次循環都要做判斷:

if board[i][j] == 1:
res[j] = 0
elif j > 0:
res[j] += res[j-1]

- 依然需要初始化res[0] = 1,res的長度要和n一樣!!

#64. Minimum Path Sum
依然是從左上角到右下角,求最短路徑和。同樣使用一維數組,因為第一行的每個元素只能從左邊那個元素過來,所以grid第一行對應的一維數組:

for i in range(1, n):
res[i] = res[i-1] + grid[0][i]

然后對一維數組更新!!m-1次:

for i in range(1, m):
for j in range(n):
if j == 0:
res[j] = res[j] + grid[i][0]
else:
res[j] = min(res[j-1], res[j]) + grid[i][j]

- 當j==0時,證明我們在更新第一列,第一列的元素只能由上邊的元素過來,所以是上邊元素的值加上當前元素的值,res[j]就是上一行的第一列的元素。
- 當j!=0時,針對每個元素,它可以從上或左的元素得到,我們就在上和左元素中選一個最小值,再加上當前元素。上邊的元素就是res[j](因為此時還沒有更新res[j]), 左邊的元素就是res[j-1]
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容