知识点
- 如果题目要求列举出所有的解,那么很有可能需要用回溯法解决。如果题目是求一个问题的最优解(通常是求最大值或最小值),或者求问题的解的数目(或判断问题是否存在解),那么这个题目有可能适合运用动态规划。
- 在采用动态规划时总是用递归的思路分析问题,即把大问题分解成小问题,再把小问题的解合起来形成大问题的解。找出描述大问题的解和小问题的解之间递归关系的状态转移方程是采用动态规划解决问题的关键所在。
- 在用代码实现动态规划的算法时,如果采用递归的代码按照从上往下的顺序求解,那么每求出一个小问题的解就缓存下来,这样下次再遇到相同的小问题就不用重复计算。另一个实现动态规划算法的方法是按照从下往上的顺序,从解决最小的问题开始,并把已经解决的小问题的解存储下来(大部分面试题都存储在一维数组或二维数组中),然后把小问题的解组合起来逐步解决大问题。
基础练习
1.爬楼梯最少的成本
Leetcode.749
解题思路
一、递归,状态转移方程 f(n) = min(f(i-2) , f(i-1)) + cost[i],缺点时间复杂度高
二、DP数组记录已经计算过的值,自上而下
三、DP数组记录已经计算过的值,自下而上
python3实现
# 一、递归
class Solution:
def helper(self, cost:List[int], i:int) -> int:
if i < 2 :
return cost[i]
return min(self.helper(cost,i-1), self.helper(cost,i-2)) + cost[i]
def minCostClimbingStairs(self, cost: List[int]) -> int:
length = len(cost)
return min(self.helper(cost,length-2),self.helper(cost,length-1))
# dp数组记录计算过的值,避免重复计算,自上而下
class Solution:
def helper(self, cost:List[int], i:int, dp:List[int]) -> None:
if i < 2 :
dp[i] = cost[i]
elif dp[i] == 0:
self.helper(cost,i-2,dp)
self.helper(cost,i-1,dp)
dp[i] = min(dp[i-2],dp[i-1]) + cost[i]
def minCostClimbingStairs(self, cost: List[int]) -> int:
length = len(cost)
dp = [0] * length
if length <= 2:
dp = cost
self.helper(cost,length - 1, dp)
return min(dp[length-2],dp[length-1])
# dp数组记录计算过的值,避免重复计算,自下而上,无递归
class Solution:
def minCostClimbingStairs(self, cost: List[int]) -> int:
length = len(cost)
dp = [0] * length
dp[0] = cost[0]
dp[1] = cost[1]
for i in range(2,length):
dp[i] = min(dp[i-1],dp[i-2]) + cost[i]
return min(dp[length-1],dp[length-2])
2.粉刷房子
Leetcode.256
解题思路
用三个数组r,b,g保存红蓝绿三种颜色房子的最小成本,由于相邻房子颜色不能一样,可得状态公式为:r( i ) = min( b( i-1) + g ( i-1) ) + cost[i][0]
python3实现
class Solution:
def minCost(self, costs: List[List[int]]) -> int:
length = len(costs)
r,b,g = [0] * length,[0] * length,[0] * length
if length == 1:
return min(costs[0],costs[1],costs[2])
r[0],b[0],g[0] = costs[0][0],costs[0][1],costs[0][2]
for i in range(1,length):
r[i] = min(b[i-1],g[i-1]) + costs[i][0]
b[i] = min(r[i-1],g[i-1]) + costs[i][1]
g[i] = min(b[i-1],r[i-1]) + costs[i][2]
return min(r[length-1],b[length-1],g[length-1])
3. 最长公共子序列
Leetcode.1143
解题思路
- 动态规划的题目不能按照演绎法去一步步地找规律,而是直接找每一个状态值和四周状态值的函数,代码很好写。
- 注意考虑特殊和边界情况
python3实现
class Solution:
def longestCommonSubsequence(self, text1: str, text2: str) -> int:
length1 = len(text1)
length2 = len(text2)
if(length1 < length2):
return self.longestCommonSubsequence(text2,text1)
dp = [[0] * (length2+1)] * (length1+1)
for i in range(length1):
for j in range(length2):
if text1[i] == text2[j]:
dp[i+1][j+1] = dp[i][j] + 1
else:
dp[i+1][j+1] = max(dp[i][j+1],dp[i+1][j])
return dp[length1][length2]
4.最小路径之和
Leetcode.64
解题思路
- 最重要的是写出状态公示,用二维DP矩阵来保存状态值,初始化第一行和第一列,再填充矩阵中其他的值。
- 注意使用. [[0]*m] * n 初始化二维矩阵时,对某个值进行修改会将整列的值进行修改,原因是浅拷贝,同一列指向同一块内存,应该改为 [[0]*m for _ in range(n)], 或者用numpy模块。
python3实现
class Solution:
def minPathSum(self, grid: List[List[int]]) -> int:
m = len(grid)
n = len(grid[0])
dp = [[0]*m for _ in range(n)]
dp[0][0] = grid[0][0]
for i in range(1,m):
dp[0][i] = dp[0][i-1]+grid[0][i]
for j in range(1,n):
dp[j][0] = dp[j-1][0]+grid[j][0]
for i in range(1,n):
for j in range(1,m):
dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + grid[i][j]
return dp[m-1][n-1]
5. 分割等和子集
Leetcode.416
解题思路
- 背包问题,从重量不同的物品中选择合适的物品放置于背包中。如果每种物品只有一个,选择放入或不放入称之为0-1背包问题,其他问题也可转化为0-1背包问题。
- 这道题可以转化为是否可以从数组中选N个数,使得其合等于数组和的一半。每个数字只能选一次。dp[i][j] 的含义为,对于第i个物品来说,如果选择不放,那么状态值等于dp[i-1][j] 的状态值,如果选择放,那么等于dp[i-1][j-nums[i]] 的值。
python3实现
class Solution:
def canPartition(self, nums: List[int]) -> bool:
n = len(nums)
sum = 0
for i in range(n):
sum += nums[i]
if sum % 2 == 1:
return False
else:
sum = sum // 2
dp = [[None] * (sum+1) for _ in range(n+1)]
for i in range(n+1):
dp[i][0] = True
for i in range(1,sum+1):
dp[0][i] = False
for i in range(1,n+1):
for j in range(1,sum+1):
dp[i][j] = dp[i-1][j]
if dp[i][j] is False and j >= nums[i-1]:
dp[i][j] = dp[i-1][j - nums[i-1]]
print(dp)
return dp[n][sum]
6. 买卖股票的最佳时机
leetcode.121
解题思路
- min表示今天前买入的最小值
- nums[i] - min 表示今天之前最小值买入,今天卖出的获利,即今天卖出的最大获利
- 比较每天的最大获利,取最大值即可
python3实现
class Solution:
def maxProfit(self, prices: List[int]) -> int:
num = len(prices)
if num <= 1:
return 0
min_num ,max_profit = prices[0], 0
for i in range(1,num):
min_num = min(prices[i],min_num)
max_profit = max(prices[i]- min_num,max_profit)
return max_profit