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),使用斐波那契的通項公式
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
針對動態規劃的思路:
- 決定存儲什么歷史信息以及用什么數據結構來存儲
- 遞推式,就是如何從存儲的歷史信息中得到當前結果
- 初始值的設定
這道題,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]