导语
leetcode刷题笔记记录,本篇博客是动态规划部分,主要记录题目包括:
Leetcode 70. 爬楼梯
题目描述
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
示例 1:
输入: n = 2
输出: 2
解释: 有两种方法可以爬到楼顶。
1. 1 阶 + 1 阶
2. 2 阶
示例 2:
输入: n = 3
输出: 3
解释: 有三种方法可以爬到楼顶。
1. 1 阶 + 1 阶 + 1 阶
2. 1 阶 + 2 阶
3. 2 阶 + 1 阶
提示:
1 <= n <= 45
进阶:改为:一步一个台阶,两个台阶,三个台阶,.......,直到 m个台阶。问有多少种不同的方法可以爬到楼顶呢?
解法
这个进阶问题可以转化为完全背包问题,1阶,2阶,.... m阶就是物品,楼顶就是背包。每一阶可以重复使用,例如跳了1阶,还可以继续跳1阶。问跳到楼顶有几种方法其实就是问装满背包有几种方法。动规五部曲如下:
- dp[i]表示爬到有i个台阶的楼顶,有dp[i]种方法。
- 递推公式:这里相当于每个数字就是重量和价值,那么递推公式为:
- 初始化:既然递归公式是 dp[i] += dp[i - j],那么dp[0] 一定为1,dp[0]是递归中一切数值的基础所在,如果dp[0]是0的话,其他数值都是0。下标非0的dp[i]初始化为0,因为dp[i]是靠dp[i-j]累计上来的,dp[i]本身为0这样才不会影响结果。
- 这是排列问题,先1后2和先2后1不一样,所以应该先遍历背包,再遍历物品。
- 打印dp数组
代码如下:
class Solution:
def climbStairs(self, n: int) -> int:
# 初始化一个长度为 n+1 的动态规划数组,用于存储到达每一阶台阶的方法数量
dp = [0] * (n + 1)
# 边界条件:到达第 0 阶台阶(起点)有 1 种方法
dp[0] = 1
# 从第 1 阶台阶开始遍历到第 n 阶台阶
for j in range(1, n + 1):
# 我们可以用 1 阶或 2 阶来到达当前 j 阶台阶,
# 所以需要遍历这两种可能性
for i in range(1, 2 + 1): # 这里直接用 range(1, 3) 也是可以的
# 检查 j-i 是否非负,这样我们就能确定能否用 i 阶来到达 j 阶
if j - i >= 0:
# dp[j] 是到达 j 阶的方法数量,dp[j-i] 是到达 j-i 阶的方法数量。
# 从 j-i 阶走 i 阶可以到达 j 阶,所以要把 dp[j-i] 加到 dp[j] 上
dp[j] += dp[j - i]
# 返回到达第 n 阶台阶的方法数量
return dp[n]
Leetcode 322. 零钱兑换
题目描述
给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。
计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。
你可以认为每种硬币的数量是无限的。
示例 1:
输入: coins = [1, 2, 5], amount = 11
输出: 3
解释: 11 = 5 + 5 + 1
示例 2:
输入: coins = [2], amount = 3
输出: -1
示例 3:
输入: coins = [1], amount = 0
输出: 0
提示:
1 <= coins.length <= 121 <= coins[i] <= 231 - 10 <= amount <= 104
解法
这道题目是求解最少的凑成方式,所以只需要将max操作修改为min操作,同时,在初始化时,数组的非零下标元素应该取最大值。打印dp数组示例(参考:代码随想录)如下:
class Solution:
def coinChange(self, coins: List[int], amount: int) -> int:
# 初始化一个动态规划数组 dp,长度为 amount+1。
# dp[i] 表示凑成总金额 i 所需的最少硬币数量。
# 初始化为 float('inf'),意味着开始时,任何金额都是无法凑成的。
dp = [0] + [float('inf')] * amount
# 遍历每一个硬币
for i in range(len(coins)):
# 从该硬币面值开始,遍历到总金额
for j in range(coins[i], amount + 1):
# 更新到达金额 j 所需的最少硬币数量。
# 两种可能:1) 不使用当前硬币 coins[i],即 dp[j] 不变;
# 2) 使用一枚当前硬币,然后加上凑成 j-coins[i] 所需的硬币数,即 dp[j-coins[i]]+1。
# 我们取两者的最小值。
dp[j] = min(dp[j - coins[i]] + 1, dp[j])
# 如果最终凑成总金额 amount 所需的硬币数仍为正无穷大,则表示无解。
if dp[amount] == float('inf'):
return -1
# 返回凑成总金额所需的最少硬币数。
return dp[amount]
Leetcode 279. 完全平方数
题目描述
给你一个整数 n ,返回 和为 n 的完全平方数的最少数量 。
完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,1、4、9 和 16 都是完全平方数,而 3 和 11 不是。
示例 1:
输入: n = 12
输出: 3
解释: 12 = 4 + 4 + 4
示例 2:
输入: n = 13
输出: 2
解释: 13 = 4 + 9
提示:
1 <= n <= 104
解法
这道题目看上去和之前套路又不一样,实际上,我们可以把1,4,9,16……这些完全平方数当做coins,最终的target当做要凑的零钱,那么这道题目就转变成了上一道题目,而且还保证一定有解(因为1也是完全平方数),所以只需要稍微修改代码,就可以得到解答,完整代码如下:
from math import sqrt
from typing import List
class Solution:
def numSquares(self, n: int) -> int:
# 初始化一个动态规划数组 dp,长度为 n+1。
# dp[i] 表示和为 i 的完全平方数的最少数量。
# 初始化为 float('inf'),意味着开始时,任何和都是无法用完全平方数凑成的。
dp = [0] + [float('inf')] * n
# 计算 n 的平方根的整数部分,这个值用于生成所有可能的完全平方数
upper = int(sqrt(n))
# 生成一个完全平方数列表,这里的完全平方数就像是"硬币"
coins = [i*i for i in range(1, upper+1)]
# 遍历每一个完全平方数(硬币)
for i in range(len(coins)):
# 从该完全平方数(硬币)面值开始,遍历到目标和
for j in range(coins[i], n+1):
# 更新凑成和 j 所需的最少完全平方数数量。
# 取两者的最小值:1) 不使用当前完全平方数,即 dp[j] 不变;
# 2) 使用一个当前完全平方数,然后加上凑成 j-coins[i] 所需的完全平方数数量,即 dp[j-coins[i]] + 1。
dp[j] = min(dp[j], dp[j - coins[i]] + 1)
# 返回凑成目标和所需的最少完全平方数数量
return dp[n]