0. 总结
一般求有多少种路径或最优路径问题,就用动态规划来解决。动态规划的几个要素:
- 递推公式:i位置的解如何由i-1位置、i-2位置(非必须)、nums[i]推导出来;
- dp向量(一维动态规划)或者dp矩阵(二维动态规划):动态规划用空间换时间,所以一定会存在dp向量或dp矩阵;但值得注意的是,一维动态规划可以将dp向量实现为常数变量,二维动态规划可以将dp矩阵实现为dp向量,这有利于降低空间复杂度;也就是说,下面1、2、3、4题标准做法是使用一个dp向量,5、6题的dp矩阵可以用dp向量替换;
- 初始值:递推的时候初始值要考虑一下;
- 递推方向:可以选择从前往后或者从后往前递推;
1. 买卖股票的最佳时机
121. best-time-to-buy-and-sell-stock
class Solution(object):
def maxProfit(self, prices):
"""
:type prices: List[int]
:rtype: int
笨办法
暴力求解,两重循环
聪明法
动态规划,前n天最大利润=max(前n-1天最大利润, 当前价格 - 前n-1天最小价格)
dp[i] = max(
dp[i-1],
price[i] - min(price_{0~i-1})
)
"""
if not prices:
return 0
min_price = prices[0]
dp = [0] * len(prices)
for i, price in enumerate(prices):
if i == 0:
continue
dp[i] = max(dp[i-1], price-min_price)
min_price = min(min_price, price)
return dp[-1]
2. 最大子序和
class Solution(object):
def maxSubArray(self, nums):
"""
:type nums: List[int]
:rtype: int
笨办法
暴力求解
聪明法
动态规划
1. 迭代n次,求出以位置i为终止的n个最大和序列:
以位置i为终止的最大子序列和 = max(以位置i-1为终止的最大子序列和+第i个数, 第i个数)
即:以第i个元素为终止时,考虑与前面合并或者断开;
2. 想要的和最大的子序列就在1所得到的候选里面;
"""
cand_sum, sum = nums[0], nums[0] # cand_sum为sum提供候选
for num in nums[1:]:
cand_sum = max(cand_sum+num, num)
sum = max(sum, cand_sum)
return sum
# dp = [0] * len(nums)
# dp[0] = nums[0]
# for i, num in enumerate(nums):
# if i == 0:
# continue
# dp[i] = max(dp[i-1]+num, num)
# return max(dp)
3. 爬楼梯
class Solution(object):
def climbStairs(self, n):
"""
:type n: int
:rtype: int
动态规划
到第i阶的方法数 = 到第i-1阶的方法数 + 到第i-2阶的方法数
因为要跨到第i阶,必须是 从第i-1阶1次跨入 或者 从第i-2阶1次跨入
dp[i] = dp[i-1] + dp[i-2]
笨办法
递归
原因以上
if n < 2:
return 1
return self.climbStairs(n-1) + self.climbStairs(n-2)
"""
if n < 3:
return n
x1, x2 = 2, 1 # x1表示到第i-1阶的方法数,x2表示到第i-2阶的方法数
x = 0
for i in range(3, n+1):
x = x1 + x2
x2 = x1
x1 = x
return x
# if n == 1:
# return 1
# dp = [0] * (n+1)
# dp[1], dp[2] = 1, 2
# for i in range(3, n+1):
# dp[i] = dp[i-1] + dp[i-2]
# return dp[-1]
4. 打家劫舍
class Solution(object):
def rob(self, nums):
"""
:type nums: List[int]
:rtype: int
聪明解法
动态规划,到i房间得到的最高金额 = max(到i-1房间最高金额, 到i-2房间最高金额+i房间的金额)
dp[i] = max(dp[i-1], dp[i-2]+nums[i])
"""
if not nums:
return 0
if len(nums) <= 2:
return max(nums)
x1, x2 = max(nums[:2]), nums[0]
x = 0
for num in nums[2:]:
x = max(x1, x2+num)
x2 = x1
x1 = x
return x
# if len(nums) == 1:
# return nums[0]
# dp = [0] * len(nums)
# dp[0], dp[1] = nums[0], max(nums[0], nums[1])
# for i in range(2, len(nums)):
# dp[i] = max(dp[i-1], dp[i-2]+nums[i])
# return dp[-1]
5. 最小路径和
class Solution:
def minPathSum(self, grid: List[List[int]]) -> int:
"""
二维动态规划
dp[i,j] = min(dp[i-1,j] + grid[i,j], dp[i,j-1] + grid[i,j])
"""
row_cnt, col_cnt = len(grid), len(grid[0])
dp = [[0] * col_cnt] * row_cnt
dp[0][0] = grid[0][0]
for i in range(0, row_cnt):
for j in range(0, col_cnt):
if i == j == 0:
continue
if i == 0:
dp[i][j] = dp[i][j-1] + grid[i][j]
elif j == 0:
dp[i][j] = dp[i-1][j] + grid[i][j]
else:
dp[i][j] = min(dp[i-1][j] + grid[i][j], dp[i][j-1] + grid[i][j])
return dp[-1][-1]
6. 不同路径
class Solution:
def uniquePaths(self, m: int, n: int) -> int:
"""
二维动态规划
dp[i][j] = dp[i-1][j] + dp[i][j-1]
"""
dp = [[0] * n] * m
dp[0][0] = 1
for i in range(m):
for j in range(n):
if i == j == 0:
continue
if i == 0:
dp[i][j] = dp[i][j-1]
elif j == 0:
dp[i][j] = dp[i-1][j]
else:
dp[i][j] = dp[i-1][j] + dp[i][j-1]
return dp[-1][-1]