目录
前言
一、动态规划:爬楼梯,杨辉三角,打家劫舍,完全平方数,零钱兑换,单词拆分,最长递增子序列,乘积最大子数组,分割等和子集,最长有效括号。
二、多维动态规划:不同路径,最小路径和,最长回文子串,最长公共子序列,编辑距离。
*** Trick:
动态规划步骤:1. 确定初始状态;2. 确定边界条件;3. 确定状态转移方程。
*** 常考:0-1背包问题(对应题目:9、14、15)
class Solution(object):
def canPartition(self, nums):
# 0-1背包问题
sums = sum(nums)
if sums % 2 != 0:
return False
target = sums // 2
n = len(nums)
dp = [[False] * (target+1) for _ in range(n)]
dp[0][0] = True
for i in range(1, n):
dp[i][0] = True
for j in range(1, target+1):
dp[0][j] = False
for i in range(1, n):
for j in range(1, target+1):
if j >= nums[i]:
dp[i][j] = dp[i-1][j] or dp[i-1][j-nums[i]]
else:
dp[i][j] = dp[i-1][j]
return dp[n-1][target]
一、动态规划
1. 爬楼梯
# 1. 初始状态:爬到第1阶、第2届台阶的方法分别有1、2种
# 2. 确定边界条件
# 3. 动态转移方程:s(n) = s(n-1) + s(n-2)
class Solution(object):
def climbStairs(self, n):
s = [1, 2]
if n <=2:
return s[n-1]
else:
for i in range(2, n):
s.append(s[-1]+s[-2])
return s[-1]
2. 杨辉三角
# 1. 初始状态:dp = [[1] * i for i in range(1, numRows+1)]
# 2. 边界条件:2<=i<numRows, 1<=j<numRows-2
# 3. 动态转移方程:dp[i][j] = dp[i][j-1] + dp[i][j]
class Solution(object):
def generate(self, numRows):
dp = [[1]*i for i in range(1, numRows+1)]
if i<2:
return dp
for i in range(2, numRows):
for j in range(1, i):
dp[i][j] = dp[i-1][j-1] + dp[i-1][j]
return dp
3. 打家劫舍
class Solution(object):
def rob(self, nums):
nums.insert(0, 0)
nums.insert(1, 0)
dp = [0 for i in range(len(nums))]
for i in range(2, len(nums)):
dp[i] = max(dp[i-1], dp[i-2]+nums[i])
return dp[-1]
4. 完全平方数
原题链接:279. 完全平方数 - 力扣(LeetCode)
# 1. 初始状态:dp = [i for i in range(n+1)]
# 2. 边界条件: wihle i-j*j >=0
# 3. 动态转移方程:dp[i] = min(dp[i-j*j]+1, dp[i])
class Solution(object):
def numSquares(self, n):
dp = [i for i in range(n+1)]
for i in range(1, n+1):
j = 1
while i-j*j >= 0:
dp[i] = min(dp[i-j*j]+1, dp[i])
j+=1
return dp[-1]
5. 零钱兑换
# 1. 初始状态:dp = [float("inf")] * (amount+1)
# 2. 边界条件:i-coin[j] >=0
# 3. 状态方程:dp[i] = min(dp[i-coin[j]] + 1, dp[i]), for j in len(coins)
class Solution(object):
def coinChange(self, coins, amount):
dp = [float("inf")] * (amount+1)
dp[0] = 0
for i in range(1, amount+1):
for j in range(len(coins)):
if i-coins[j] >= 0:
dp[i] = min(dp[i-coins[j]] + 1, dp[i])
return dp[-1] if dp[-1]!=float("inf") else -1
6. 单词拆分
class Solution(object):
def wordBreak(self, s, wordDict):
dp = [0] + [False for i in range(1, len(s)+1)]
for i in range(len(s)+1):
if dp[i] == True:
for j in range(i+1, len(s)+1):
if s[i:j] in wordDict:
dp[j] = True
return dp[-1]
7. 最长递增子序列
原题链接:300. 最长递增子序列 - 力扣(LeetCode)
class Solution(object):
def lengthOfLIS(self, nums):
dp = [1 for i in range(len(nums))]
for i in range(len(nums)):
for j in range(i):
if nums[i] > nums[j]:
dp[i] = max(dp[j]+1, dp[i])
return max(dp)
8. 乘积最大子数组
原题链接:152. 乘积最大子数组 - 力扣(LeetCode)
class Solution(object):
def maxProduct(self, nums):
# 初始状态:max_pre = min_pre = num[0]
# 状态转移方程:max_pre = max(max_pre*nums[i]), min_pre*nums[i], nums[i])
res = nums[0]
max_pre = min_pre = nums[0]
for i in range(1, len(nums)):
max_pre_ = max_pre * nums[i]
min_pre_ = min_pre * nums[i]
max_pre = max(max_pre_, min_pre_, nums[i])
min_pre = min(max_pre_, min_pre_, nums[i])
res = max(res, max_pre)
return res
9. 分割等和子集
原题链接:416. 分割等和子集 - 力扣(LeetCode)
class Solution(object):
def canPartition(self, nums):
# 0-1背包问题
sums = sum(nums)
if sums % 2 != 0:
return False
target = sums // 2
n = len(nums)
dp = [[False] * (target+1) for _ in range(n)]
dp[0][0] = True
for i in range(1, n):
dp[i][0] = True
for j in range(1, target+1):
dp[0][j] = False
for i in range(1, n):
for j in range(1, target+1):
if j >= nums[i]:
dp[i][j] = dp[i-1][j] or dp[i-1][j-nums[i]]
else:
dp[i][j] = dp[i-1][j]
return dp[n-1][target]
10. 最长有效括号
原题链接:32. 最长有效括号 - 力扣(LeetCode)
class Solution(object):
def longestValidParentheses(self, s):
stack = [-1]
res = 0
for i in range(len(s)):
if s[i] == '(':
stack.append(i)
else:
stack.pop()
if not stack:
stack.append(i)
else:
res = max(res, i-stack[-1])
return res
二、多维动态规划
11. 不同路径
class Solution(object):
def uniquePaths(self, m, n):
# 初始状态dp[i][0] = 1, dp[0][j] = 1
# 状态转移方程:dp[i][j] = dp[i-1][j] + dp[i][j-1]
dp = [[0] * n for _ in range(m)]
for i in range(m):
dp[i][0] = 1
for j in range(n):
dp[0][j] = 1
for i in range(1, m):
for j in range(1, n):
dp[i][j] = dp[i-1][j] + dp[i][j-1]
return dp[-1][-1]
12. 最小路径和
class Solution(object):
def minPathSum(self, grid):
# dp[i][0] = dp[i-1] + grid[i][0], dp[0][j] = dp[0][j-1] + grid[0][j]
# dp[i][j] = min(dp[i-1][j] + grid[i][j], dp[i][j-1]+ grid[i][j])
m, n = len(grid), len(grid[0])
dp = [[0] * n for _ in range(m)]
dp[0][0] = grid[0][0]
for i in range(1, m):
dp[i][0] = dp[i-1][0] + grid[i][0]
for j in range(1, n):
dp[0][j] = dp[0][j-1] + grid[0][j]
for i in range(1, m):
for j in range(1, n):
dp[i][j] = min(dp[i-1][j] + grid[i][j], dp[i][j-1]+ grid[i][j])
return dp[-1][-1]
13. 最长回文子串
class Solution(object):
def longestPalindrome(self, s):
n = len(s)
dp = [[False] * n for _ in range(n)]
start, max_len = 0, 0
for right in range(n):
for left in range(right+1):
span = right - left + 1
if span == 1:
dp[left][right] = True
elif span == 2:
dp[left][right] = s[left] == s[right]
else:
dp[left][right] = dp[left+1][right-1] and s[left] == s[right]
if dp[left][right]:
if span > max_len:
max_len = span
start = left
return s[start: start+max_len]
14. 最长公共子序列
原题链接:1143. 最长公共子序列 - 力扣(LeetCode)
class Solution(object):
def longestCommonSubsequence(self, text1, text2):
# 0-1 背包问题
# if t1 == t2: dp[i+1][j+1] = dp[i][j] + 1
# else: dp[i+1][j+1] = max(dp[i+1][j], dp[i][j+1])
m, n = len(text1), len(text2)
dp = [[0]* (n+1) for _ in range(m+1)]
for i, t1 in enumerate(text1):
for j, t2 in enumerate(text2):
if t1 == t2:
dp[i+1][j+1] = dp[i][j] + 1
else:
dp[i+1][j+1] = max(dp[i+1][j], dp[i][j+1])
return dp[-1][-1]
15. 编辑距离
class Solution(object):
def minDistance(self, word1, word2):
# 0-1 背包问题
# 动态转移方程:dp[i][j] = min(dp[i][j-1], dp[i-1][j], dp[i-1][j-1]) + 1分别对应插入、删除、替换操作
"""
0 r o s
0 0 1 2 3
h 1
o 2
r 3
s 4
e 5
"""
m, n = len(word1), len(word2)
dp = [[0] * (n+1) for _ in range(m+1)]
for i in range(1, m+1):
dp[i][0] = i
for j in range(1, n+1):
dp[0][j] = j
for i, w1 in enumerate(word1):
for j, w2 in enumerate(word2):
if w1 == w2:
dp[i+1][j+1] = dp[i][j]
else:
dp[i+1][j+1] = min(dp[i+1][j], dp[i][j+1], dp[i][j]) + 1
return dp[-1][-1]