終于講到動態規劃了,剛剛面完依圖,就考了動態規劃,在面試官的提示下才寫出來,要是認真刷過題就好了。
Leetcode 53. 最大子序和
給定一個整數數組 nums ,找到一個具有最大和的連續子數組(子數組最少包含一個元素),返回其最大和。
示例:
輸入: [-2,1,-3,4,-1,2,1,-5,4],
輸出: 6
解釋: 連續子數組 [4,-1,2,1] 的和最大,為 6。
進階:
如果你已經實現復雜度為 O(n) 的解法,嘗試使用更為精妙的分治法求解。
解題思路一:動態規劃。比較前 i-1 個數的最大子序和+第i個數的和,與第i個數本身的大小比較。
解題思路二:
class Solution(object):
def maxSubArray(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
dp = nums
dp[0] = nums[0]
ans = dp[0]
for i in range(1, len(nums)):
dp[i] = max(dp[i-1] + nums[i], nums[i])
if dp[i] > ans:
ans = dp[i]
return ans
Leetcode 120. 三角形最小路徑和
給定一個三角形,找出自頂向下的最小路徑和。每一步只能移動到下一行中相鄰的結點上。
例如,給定三角形:
[
[2],
[3,4],
[6,5,7],
[4,1,8,3]
]
自頂向下的最小路徑和為 11(即,2 + 3 + 5 + 1 = 11)。
說明:
如果你可以只使用 O(n) 的額外空間(n 為三角形的總行數)來解決這個問題,那么你的算法會很加分。
解題思路:這個題很容易從上往下想,其實要自下而上來思考。
class Solution(object):
def minimumTotal(self, triangle):
"""
:type triangle: List[List[int]]
:rtype: int
"""
n = len(triangle)
i = n - 2
while i >= 0:
for j in range(i+1):
triangle[i][j] += min(triangle[i+1][j], triangle[i+1][j+1])
i -= 1
return triangle[0][0]
Leetcode 62. 不同路徑
一個機器人位于一個 m x n 網格的左上角 (起始點在下圖中標記為“Start” )。
機器人每次只能向下或者向右移動一步。機器人試圖達到網格的右下角(在下圖中標記為“Finish”)。
問總共有多少條不同的路徑?
例如,上圖是一個7 x 3 的網格。有多少可能的路徑?
說明:m 和 n 的值均不超過 100。
示例 1:
輸入: m = 3, n = 2
輸出: 3
示例 2:
輸入: m = 7, n = 3
輸出: 28
解題思路:動態規劃方法的經典應用,每一個點上面的路徑總數都等于它上面和左邊的點的路徑總數之和。注意這里的mn是列行而不是行*列。
class Solution(object):
def uniquePaths(self, m, n):
"""
:type m: int
:type n: int
:rtype: int
"""
if m == 1 or n == 1:
return 1
dp = [[1 for _ in range(m)] for _ in range(n)]
for i in range(1, n):
for j in range(1, m):
dp[i][j] = dp[i-1][j] + dp[i][j-1]
return dp[n-1][m-1]
Leetcode 63. 不同路徑II
網格中的障礙物和空位置分別用 1 和 0 來表示。
說明:m 和 n 的值均不超過 100。
示例 1:
輸入:
[
[0,0,0],
[0,1,0],
[0,0,0]
]
輸出: 2
解題思路:創建一個dp的數組,全部為0,第一位為1-input[0][0],遞推公式為如果input[i][i] == 1的話,則dp[i][j]為0,否則dp[i][j] = dp[i-1][j]+dp[i][j-1]
class Solution(object):
def uniquePathsWithObstacles(self, obstacleGrid):
"""
:type obstacleGrid: List[List[int]]
:rtype: int
"""
m = len(obstacleGrid)
n = len(obstacleGrid[0])
if m == 0:
return 0
if n == 0:
return 0
dp = [[0 for _ in range(n)] for _ in range(m)]
dp[0][0] = 1 - obstacleGrid[0][0]
for i in range(m):
for j in range(n):
if obstacleGrid[i][j] == 1:
dp[i][j] = 0
else:
if i > 0:
dp[i][j] += dp[i-1][j]
if j > 0:
dp[i][j] += dp[i][j-1]
return dp[m-1][n-1]
Leetcode 91. 解碼方法
一條包含字母 A-Z 的消息通過以下方式進行了編碼:
'A' -> 1
'B' -> 2
...
'Z' -> 26
給定一個只包含數字的非空字符串,請計算解碼方法的總數。
示例 1:
輸入: "12"
輸出: 2
解釋: 它可以解碼為 "AB"(1 2)或者 "L"(12)。
示例 2:
輸入: "226"
輸出: 3
解釋: 它可以解碼為 "BZ" (2 26), "VF" (22 6), 或者 "BBF" (2 2 6) 。
解題思路:注意這里要把dp的長度擴大一位,相應的dp中對應的答案的序號也會后移一位。
class Solution(object):
def numDecodings(self, s):
"""
:type s: str
:rtype: int
"""
if s[0] == '0' or s == None:
return 0
dp = [0] * (len(s) + 1)
dp[0] = 1
for i in range(1, len(dp)):
if s[i-1] != '0':
dp[i] = dp[i-1]
if i != 1 and '09' < s[i-2:i] < '27':
dp[i] += dp[i-2]
return dp[-1]
Leetcode 198. 打家劫舍
(跪了跪了,去依圖現場面試的就是這道題,寫了好久好久,哭唧唧,應該多刷刷題的)
你是一個專業的小偷,計劃偷竊沿街的房屋。每間房內都藏有一定的現金,影響你偷竊的唯一制約因素就是相鄰的房屋裝有相互連通的防盜系統,如果兩間相鄰的房屋在同一晚上被小偷闖入,系統會自動報警。給定一個代表每個房屋存放金額的非負整數數組,計算你在不觸動警報裝置的情況下,能夠偷竊到的最高金額。
示例 1:
輸入: [1,2,3,1]
輸出: 4
解釋: 偷竊 1 號房屋 (金額 = 1) ,然后偷竊 3 號房屋 (金額 = 3)。
偷竊到的最高金額 = 1 + 3 = 4 。
示例 2:
輸入: [2,7,9,3,1]
輸出: 12
解釋: 偷竊 1 號房屋 (金額 = 2), 偷竊 3 號房屋 (金額 = 9),接著偷竊 5 號房屋 (金額 = 1)。
偷竊到的最高金額 = 2 + 9 + 1 = 12 。
解題思路:這里的動態規劃需要兩個變量,一個是偷前面一家,一個是不偷前面一家。
def rob(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
n = len(nums)
if n == 0:
return 0
f = nums[0]
g = 0
for i in range(1, n):
last_f = f
last_g = g
f = g + nums[i]
g = max(last_f, last_g)
return max(f, g)
Leetcode 300. 最長上升子序列
給定一個無序的整數數組,找到其中最長上升子序列的長度。
示例:
輸入: [10,9,2,5,3,7,101,18]
輸出: 4
解釋: 最長的上升子序列是 [2,3,7,101],它的長度是 4。
說明:
可能會有多種最長上升子序列的組合,你只需要輸出對應的長度即可。
你算法的時間復雜度應該為 O(n2) 。
進階: 你能將算法的時間復雜度降低到 O(n log n) 嗎?
解題思路:用數組dp[i]記錄以nums[i]結尾(即nums[i]為最后一個數字)的最長遞增子序列的長度,則遞推方程為dp[i]=max(dp[j]+1),其中要求1≤j<i且nums[j]<nums[i]。
時間復雜度分析:對每個i(1≤i≤n),都需要從1遍歷到i,則時間復雜度為O(n2),空間復雜度的話需要一個額外的dp數組,空間復雜度為O(n2)。
class Solution(object):
def lengthOfLIS(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
n = len(nums)
if n <= 1:
return n
dp = [1 for _ in range(n)]
length = 1
for i in range(1, n):
for j in range(i):
if nums[j] < nums[i]:
dp[i] = max(dp[i], dp[j] + 1)
length = max(length, dp[i])
return length
Leetcode 72. 編輯距離
你可以對一個單詞進行如下三種操作:
插入一個字符
刪除一個字符
替換一個字符
示例 1:
輸入: word1 = "horse", word2 = "ros"
輸出: 3
解釋:
horse -> rorse (將 'h' 替換為 'r')
rorse -> rose (刪除 'r')
rose -> ros (刪除 'e')
示例 2:
輸入: word1 = "intention", word2 = "execution"
輸出: 5
解釋:
intention -> inention (刪除 't')
inention -> enention (將 'i' 替換為 'e')
enention -> exention (將 'n' 替換為 'x')
exention -> exection (將 'n' 替換為 'c')
exection -> execution (插入 'u')
解題思路:
class Solution(object):
def minDistance(self, word1, word2):
"""
:type word1: str
:type word2: str
:rtype: int
"""
n = len(word1)
m = len(word2)
if n == 0:
return m
if m == 0:
return n
f = [[0 for _ in range(m + 1)] for _ in range(n + 1)]
for i in range(1, n+1):
f[i][0] = i
for i in range(1, m+1):
f[0][i] = i
for i in range(1, n+1):
for j in range(1, m+1):
if word1[i-1] == word2[j-1]:
f[i][j] = f[i-1][j-1]
else:
f[i][j] = f[i-1][j-1] + 1
f[i][j] = min(f[i-1][j] + 1, f[i][j])
f[i][j] = min(f[i][j-1] + 1, f[i][j])
return f[n][m]
Leetcode 518. 零錢兌換II
給定不同面額的硬幣和一個總金額。寫出函數來計算可以湊成總金額的硬幣組合數。假設每一種面額的硬幣有無限個。
示例 1:
輸入: amount = 5, coins = [1, 2, 5]
輸出: 4
解釋: 有四種方式可以湊成總金額:
5=5
5=2+2+1
5=2+1+1+1
5=1+1+1+1+1
示例 2:
輸入: amount = 3, coins = [2] 輸出: 0
你可以假設:
0 <= amount (總金額) <= 5000
1 <= coin (硬幣面額) <= 5000
硬幣種類不超過 500 種
結果符合 32 位符號整數
解題思路:
class Solution(object):
def change(self, amount, coins):
"""
:type amount: int
:type coins: List[int]
:rtype: int
"""
n = len(coins)
dp = [0 for _ in range(amount+1)]
dp[0] = 1
for i in range(n):
j = coins[i]
while j <= amount:
dp[j] += dp[j-coins[i]]
j += 1
return dp[amount]
Leetcode 664. 奇怪的打印機
有臺奇怪的打印機有以下兩個特殊要求:
打印機每次只能打印同一個字符序列。
每次可以在任意起始和結束位置打印新字符,并且會覆蓋掉原來已有的字符。
給定一個只包含小寫英文字母的字符串,你的任務是計算這個打印機打印它需要的最少次數。
示例 1:
輸入: "aaabbb"
輸出: 2
解釋: 首先打印 "aaa" 然后打印 "bbb"。
示例 2:
輸入: "aba"
輸出: 2
解釋: 首先打印 "aaa" 然后在第二個位置打印 "b" 覆蓋掉原來的字符 'a'。
解題思路:
class Solution(object):
def strangePrinter(self, s):
"""
:type s: str
:rtype: int
"""
n = len(s)
if n == 0:
return 0
dp = [[0 for _ in range(n)] for _ in range(n)]
for i in range(n):
dp[i][i] = 1
for length in range(1, n):
for i in range(n-length):
j = i + length
dp[i][j] = 1 + dp[i+1][j]
for k in range(i+1, j):
if s[i] == s[k]:
dp[i][j] = min(dp[i][j], dp[i][k-1] + dp[k+1][j])
if s[i] == s[j]:
dp[i][j] = min(dp[i][j], dp[i+1][j])
return dp[0][n-1]
Leetcode 10. 正則表達式
給你一個字符串 s 和一個字符規律 p,請你來實現一個支持 '.' 和 '' 的正則表達式匹配。
'.' 匹配任意單個字符
'' 匹配零個或多個前面的那一個元素
所謂匹配,是要涵蓋 整個 字符串 s的,而不是部分字符串。
說明:
s 可能為空,且只包含從 a-z 的小寫字母。
p 可能為空,且只包含從 a-z 的小寫字母,以及字符 . 和 。
示例 1:
輸入:
s = "aa"
p = "a"
輸出: false
解釋: "a" 無法匹配 "aa" 整個字符串。
示例 2:
輸入:
s = "aa"
p = "a"
輸出: true
解釋: 因為 '' 代表可以匹配零個或多個前面的那一個元素, 在這里前面的元素就是 'a'。因此,字符串 "aa" 可被視為 'a' 重復了一次。
示例 3:
輸入:
s = "ab"
p = "."
輸出: true
解釋: "." 表示可匹配零個或多個('')任意字符('.')。
示例 4:
輸入:
s = "aab"
p = "cab"
輸出: true
解釋: 因為 '' 表示零個或多個,這里 'c' 為 0 個, 'a' 被重復一次。因此可以匹配字符串 "aab"。
示例 5:
輸入:
s = "mississippi"
p = "misisp."
輸出: false
解題思路:
class Solution(object):
def isMatch(self, s, p):
"""
:type s: str
:type p: str
:rtype: bool
"""
n = len(s)
m = len(p)
s = ' ' + s
p = ' ' + p
f = [[False for _ in range(m + 1)] for _ in range(n + 1)]
f[0][0] = True
for i in range(n + 1):
for j in range(1, m + 1):
if i > 0 and (s[i] == p[j] or p[j] == '.'):
f[i][j] = f[i][j] or f[i-1][j-1]
if p[j] == '*':
f[i][j] = f[i][j] or f[i][j-2]
if i > 0 and (s[i] == p[j-1] or p[j-1] == '.'):
f[i][j] = f[i][j] or f[i-1][j]
return f[n][m]