- 给定一个矩阵m,从左上角开始每次只能向右或者向下走,最后到达右下角的位置,路径上所有的数字累加起来就是路径和,返回所有的路径中最小的路径和。
思路:经典动态规划,生成辅助矩阵,二维动态规划。
dp[i][j] 表示从左上角走到(i,j)位置的最小路径和,到达dp[i][j]有两种方案,从左边的点dp[i][j-1]向右走,或者从上边的点dp[i-1][j]向下走。
def minSum(matrix):
n = len(matrix)
m = len(matrix[0])
grid = [[None] * m for _ in range(n)]
print(grid)
for i in range(n):
for j in range(m):
if i-1 >=0 and j-1 >=0:
up = grid[i - 1][j]
left = grid[i][j - 1]
grid[i][j] = min(up, left) + matrix[i][j] #重点动规公式
elif i - 1 >= 0 and j - 1 < 0:
grid[i][j] = grid[i - 1][j] + matrix[i][j]
elif i-1<0 and j-1>=0:
grid[i][j] = grid[i][j - 1] + matrix[i][j]
else:
grid[i][j] = matrix[i][j]
print(i, grid)
return grid[-1][-1]
matrix = [[1,3,5,9],[8,1,3,4],[5,0,6,1],[8,8,4,0]]
print(minSum(matrix))
- 给定数组arr,返回arr的最长递增子序列长度。比如arr=[2,1,5,3,6,4,8,9,7],最长递增子序列为[1,3,4,8,9],返回子序列长度5.
思路:同 leetcode 300
dp[i]表示必须以arr[i]这个数结尾的情况下,arr[0,…,i]中的最大递增子序列长度。
def lengthOfLIS(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
dp = [1] * len(nums)
for i in range(1, len(nums)):
for j in range(0, i):
if nums[i] > nums[j]:
dp[i] = max(dp[i], dp[j]+1)
return max(dp)
- 给定两个字符串,str1和str2,返回两个字符串的最长公共子序列。
思路:同leetcode 1143
经典二维动态规划,dp[i][j]表示str1[0:i]与str2[0:j]的最长公共子序列长度。
变体:最长连续公共子序列(最长连续子串),对子串要求更严格,当 text1[i] != text2[j]时,dp[i][j] = 0。
def longestCommonSubsequence(self, text1, text2):
"""
:type text1: str
:type text2: str
:rtype: int
"""
n = len(text1)
m = len(text2)
dp = [[0]*m for _ in range(n)]
for i in range(n):
for j in range(m):
if i-1>=0 and j-1>=0:
if text1[i] == text2[j]:
dp[i][j] = max(dp[i-1][j-1]+1, dp[i-1][j], dp[i][j-1])
else:
dp[i][j] = max(dp[i-1][j], dp[i][j-1])
elif i-1>=0 and j-1<0:
if text1[i] == text2[j]:
dp[i][j] = 1
else:
dp[i][j] = dp[i-1][j]
elif i-1<0 and j-1>=0:
if text1[i] == text2[j]:
dp[i][j] = 1
else:
dp[i][j] = dp[i][j-1]
else:
if text1[i] == text2[j]:
dp[i][j] = 1
else:
dp[i][j] = 0
return dp[-1][-1]
- 一个背包有一定的承重W,有N件物品,每件都有自己的价值,记录在数组v中,也都有自己的重量,记录在数组w中,每件物品只能选择要装入背包还是不装入背包,要求在不超过背包承重的前提下,选出物品的总价值最大。
思路:经典动态规划,背包问题。
def bag01(values, weights,W):
"""
values: 物品的价值数组
weights: 物品的重量数组
W: 背包的承重
"""
N = len(values)
dp = [[0]*(W+1) for _ in range(N)] #N*(W+1)
for i in range(N):
for j in range(W+1):
# last1 = dp[i-1][j]
# last2 = dp[i-1][j-weights[i]] + values[i]
if i-1>=0 and j-weights[i]>=0:
dp[i][j] = max(dp[i-1][j], dp[i-1][j-weights[i]] + values[i])
elif i-1>=0 and j-weights[i]<0:
dp[i][j] = dp[i-1][j]
elif i-1<0 and j-weights[i]>=0:
dp[i][j] = values[i]
else:
dp[i][j] = 0
print(dp)
return dp[-1][-1]
- 给定两个字符串 str1 和 str2,再给定三个整数 ic、dc和rc,分别代表插入、删除和替换一个字符的代价。返回将 str1 编辑成 str2 的最小代价。
思路:给定二维动规矩阵dp[M+1][N+1],其中dp[i][j]表示从str1[0:i-1]到str2[0:j-1]编辑的最小代价。一共有四种可能情况:
str1[0:i-1]和str2[0:j-2] + 插入一个字符,插入j-1
str1[0:i-2]和str2[0:j-1] + 删除一个字符,删除i-1
i-1!=j-1: str1[0:i-2]和str2[0:j-2] + 替换一个字符,i-1替换j-1
i-1==j-1: 直接使用str1[0:i-2]和str2[0:j-2] 的代价
参考leetcode 72
def editCost(str1, str2, ic, dc, rc):
m = len(str1)
n = len(str2)
dp = [[0]*(n+1) for _ in range(m+1)]
# 第一列,将str1转为"",删除掉str1的元素
for i in range(1,m+1):
dp[i][0] = dc*i
# 第一行,将""转为str2,插入str2的元素
for j in range(1,n+1):
dp[0][j] = ic*j
#其他,四种情况
for i in range(1,m+1):
for j in range(1,n+1):
dc_cost = dp[i-1][j] + dc
ic_cost = dp[i][j-1] + ic
rc_cost = dp[i-1][j-1]
if str1[i-1]!=str2[j-1]:
rc_cost = rc_cost + rc
dp[i][j] = min(dc_cost, ic_cost, rc_cost)
return dp[-1][-1]
str1 = "abcdef"
str2 = "abcddd"
print(editCost(str1,str2,1,1,1))
- 给定数组arr,arr中所有值为正数且不重复,每个值代表一种面值的货币,每种面值的货币可以使用任意张。再给定整数aim代表要找的钱数,求换钱有多少种方法?
leetcode 322 零钱兑换 题目类似
牛客 CD19 换钱的方法数 二维动态规划,超时
def changeNum(coins, aim):
aim = int(aim)
n = len(coins)
dp = [[0] * (aim + 1) for _ in range(n)]
# 第一列填充,aim=0时
for i in range(0, n):
dp[i][0] = 1
# 第一行填充,仅使用一种硬币时
k = aim // coins[0]
for i in range(0, k + 1):
dp[0][i * coins[0]] = 1
for line in dp:
print(line)
# print(dp)
# 填充剩余
for i in range(1, n):
for j in range(1, aim + 1):
if j - coins[i] >= 0:
dp[i][j] = dp[i - 1][j] + dp[i][j - coins[i]]
else:
dp[i][j] = dp[i - 1][j]
return dp[-1][-1]
coins = [5,10,25,1]
n = 15
print(changeNum(coins,n))
优化成一维动态规划
def changeNumDim1(coins, aim):
aim = int(aim)
n = len(coins)
dp = [0] * (aim + 1)
dp[0] = 1
for i in range(n):
for j in range(coins[i], aim+1):
dp[j] = dp[j] + dp[j-coins[i]]
print(dp)
return dp[-1]
- 查找两个数组的最长公共子数组并输出这个公共子数组,要求子数组是连续的。
思路:dp[i][j]表示以 i,j 结尾的最长公共子数组, 如果a[i]!=b[j]. dp[i][j]=0.
def findLength(self, nums1, nums2):
m = len(nums1)
n = len(nums2)
dp = [[0]*(n+1) for _ in range(m+1)] #构建dp数组记忆
result = -1
idx = -1
for i in range(1,m+1):
for j in range(1,n+1):
if nums1[i-1] == nums2[j-1]:
dp[i][j] = dp[i-1][j-1] + 1
else:
dp[i][j] = 0
if dp[i][j] > result:
result = dp[i][j]
idx = i
print('result', nums1[i-result:i+1])
return result