动态规划核心就是列出状态转移方程来求全局最优,故下面每道题目都列出相应的状态转移方程。
题目:给你一个可装载重量为 W 的背包和 N 个物品,每个物品有重量和价值两个属性。其中第 i 个物品的重量为 wt[i],价值为 val[i],现在让你用这个背包装物品,最多能装的价值是多少?
举个简单的例子,输入如下:
N = 3, W = 4
wt = [2, 1, 3]
val = [4, 2, 3]
状态转移方程:
dp[i][w]的定义如下:对于前i个物品,当前背包的容量为w,这种情况下可以装的最大价值是dp[i][w]
如果你没有把这第i个物品装入背包,那么很显然,最大价值dp[i][w]=dp[i-1][w]。你不装嘛,那就继承之前的结果。
如果你把这第i个物品装入了背包,那么dp[i][w]=dp[i-1][w-wt[i-1]] + val[i-1]
509.斐波那契数
dp[i] = dp[i - 1] + dp[i - 2]
322.零钱兑换
F(i) 为组成金额 i 所需最少的硬币数量
F(i)=min F(i-coin) +1
class Solution:
def coinChange(self, coins: List[int], amount: int) -> int:
dp=[float('inf')]*(amount+1)
dp[0]=0
for coin in coins:
for x in range(coin,amount+1):
dp[x]=min(dp[x],dp[x-coin]+1)
return dp[amount] if dp[amount]!=float('inf') else -1
494.目标和
设置一个哈希表(字典),键是一个元祖,元祖第一位是目前的和,第二位是目前的位数。值是这个元祖推导到最后能有多少个解。
因为符号要么全正,要么全负,所以元祖第二位的取值范围是 -sum(nums) ~ sum(nums)
状态转移公式:
dp[(i,j)] = dp.get((i - 1, j - nums[i]), 0) + dp.get((i - 1, j + nums[i]), 0)
class Solution:
def findTargetSumWays(self, nums: List[int], S: int) -> int:
n=len(nums)
d={(0,0):1}
for i in range(1,n+1):
for j in range(-sum(nums),sum(nums)+1):
d[(i,j)]=d.get((i-1,j-nums[i-1]),0)+d.get((i-1,j+nums[i-1]),0)
return d.get((n,S),0)
class Solution:
def lengthOfLIS(self, nums: List[int]) -> int:
if not nums:
return 0
dp = []
for i in range(len(nums)):
dp.append(1)
for j in range(i):
if nums[i] > nums[j]:
dp[i] = max(dp[i], dp[j] + 1)
return max(dp)
53.最大子序和
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
n=len(nums)
dp=[-float('inf')]*n
dp[0]=nums[0]
for i in range(1,n):
dp[i]=max(dp[i-1]+nums[i],nums[i])
return max(dp)
class Solution:
def canPartition(self, nums: List[int]) -> bool:
n=len(nums)
target=sum(nums)
if(target%2!=0):
return False
target//=2
dp=[[False]*(target+1) for _ in range(n)]
dp[0][0]=True
for i in range(1,target+1):
if(nums[0]==i):
dp[0][i]=True
break
for i in range(1,n):
for j in range(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[-1][-1]
class Solution:
def change(self, amount: int, coins: List[int]) -> int:
dp = [0] * (amount + 1)
dp[0] = 1
for coin in coins:
for x in range(coin, amount + 1):
dp[x] += dp[x - coin]
return dp[amount]
class Solution:
def minDistance(self, word1: str, word2: str) -> int:
n1 = len(word1)
n2 = len(word2)
dp = [[0] * (n2 + 1) for _ in range(n1 + 1)]
# 第一行
for j in range(1, n2 + 1):
dp[0][j] = dp[0][j-1] + 1
# 第一列
for i in range(1, n1 + 1):
dp[i][0] = dp[i-1][0] + 1
for i in range(1, n1 + 1):
for j in range(1, n2 + 1):
if word1[i-1] == word2[j-1]:
dp[i][j] = dp[i-1][j-1]
else:
dp[i][j] = min(dp[i][j-1], dp[i-1][j], dp[i-1][j-1] ) + 1
#print(dp)
return dp[-1][-1]
鸡蛋掉落
转换思想,让dp[i][j]表示,有i个鸡蛋,扔j次,最大能确定的楼层数。
dp[i][j] = dp[i-1][j-1] + dp[i][j-1] + 1
因为扔了一次,所以后面还剩j-1次机会。碎了的话只有i-1个鸡蛋(用i-1个鸡蛋,j-1次机会实验这层楼之下的,看能确定几层),没碎还有i个鸡蛋(用i个鸡蛋,j-1次实验机会,实验这层楼之上的,看有几层), 然后总得i个鸡蛋,j次机会能确定的楼数,就是下面的楼数加上面的再加本次的1.
时间复杂度:O(KN)
空间复杂度:O(KN) O(K)(因为状态只和左边和左上有关,因此可以向左打,简化为1维数组,然后遍历的时候倒序就行了(使得dp[i]是原来的dp[i][j-1], dp[i-1]是dp[i-1][j-1], 根据这俩算新的dp[i],新的dp[i]就相当于dp[i][j])
class Solution:
def superEggDrop(self, K: int, N: int) -> int:
dp = [0] * (K+1)
cnt = 0
while dp[-1]<N:
for i in reversed(range(1,K+1)):
dp[i] = dp[i]+dp[i-1]+1
# print(dp)
cnt+=1
return cnt
class Solution:
def maxCoins(self, nums: List[int]) -> int:
# reframe problem as before
nums = [1] + nums + [1]
n = len(nums)
# dp will store the results of our calls
dp = [[0] * n for _ in range(n)]
# iterate over dp and incrementally build up to dp[0][n-1]
for left in range(n-2, -1, -1):
for right in range(left+2, n):
# same formula to get the best score from (left, right) as before
dp[left][right] = max(nums[left] * nums[i] * nums[right] + dp[left][i] + dp[i][right] for i in range(left+1, right))
return dp[0][n-1]
class Solution:
def longestCommonSubsequence(self, text1: str, text2: str) -> int:
m, n = len(text1), len(text2)
# 构建 DP table 和 base case
dp = [[0] * (n + 1) for _ in range(m + 1)]
# 进行状态转移
for i in range(1, m + 1):
for j in range(1, n + 1):
if text1[i - 1] == text2[j - 1]:
# 找到一个 lcs 中的字符
dp[i][j] = 1 + dp[i-1][j-1]
else:
dp[i][j] = max(dp[i-1][j], dp[i][j-1])
return dp[-1][-1]
class Solution:
def longestPalindromeSubseq(self, s: str) -> int:
#dp[j][i]从j到i最长回文子序列的长度
n = len(s)
dp = [[0] * n for _ in range(n)]
for i in range(n):
dp[i][i] = 1
for j in range(i - 1, -1, -1):
if s[j] == s[i]:
dp[j][i] = 2 + dp[j + 1][i - 1]
else:
dp[j][i] = max(dp[j + 1][i], dp[j][i - 1])
return dp[0][n - 1]