文章目录
跟随carl代码随想录刷题
语言:python
递推公式:dp[j] += dp[j - nums[i]]
- 如果求组合数,那外层for循环遍历物品,内层for循环遍历背包。
- 如果求排列数,那外层for遍历背包,内层for循环遍历物品。
494. 目标和
题目:给你一个整数数组 nums 和一个整数 target 。
向数组中的每个整数前添加 ‘+’ 或 ‘-’ ,然后串联起所有整数,可以构造一个 表达式 :
例如,nums = [2, 1] ,可以在 2 之前添加 ‘+’ ,在 1 之前添加 ‘-’ ,然后串联起来得到表达式 “+2-1” 。
返回可以通过上述方法构造的、运算结果等于 target 的不同 表达式 的数目。
👉示例 1:
输入:nums = [1,1,1,1,1], target = 3
输出:5
解释:一共有 5 种方法让最终目标和为 3 。
-1 + 1 + 1 + 1 + 1 = 3
+1 - 1 + 1 + 1 + 1 = 3
+1 + 1 - 1 + 1 + 1 = 3
+1 + 1 + 1 - 1 + 1 = 3
+1 + 1 + 1 + 1 - 1 = 3
👉示例 2:
输入:nums = [1], target = 1
输出:1
题目分析
思路:
想要使表达结果为target
那么target一定可以表示成left组合 + right组合 = target
因此,公式为:left - (sum - left) = target
-> left = (target + sum) / 2
因此,要找出和为left的组合
定义
- left为
加法组合
- right为
减法组合
- 注意:left必须是整数。即:if
target + sum / 2
% 2 == 1: return 0 # 无解 - 如果
if abs(target) > sum
:return 0
# 也是没有方案的
因为本题中元素只能用一次,所以也是01背包问题
动态规划五部曲
- 确定dp数组以及下标的含义
- dp[j] 表示:
填满
j(包括j)这么大容积的包,有dp[j]种方法
- dp[j] 表示:
- 确定递推公式
- 求组合类问题的公式,都是类似这种:dp[j] += dp[j - nums[i]]
- 把所有的dp[j]累加起来
- dp数组如何初始化
- dp[0]一定要初始化为1【不能初始化为0!因为dp[0]是一切推导的起点,如果dp[0]=0,那么后续递推结果都是0】
- 确定遍历顺序
- nums放在外循环
- target放在内循环
- 举例推导dp数组
- 输入nums[1, 1, 1, 1, 1], target = 3
- bag_size = (target + sum)/2 = (3+5)/2 = 4
- dp数组的状态变化如下:
- ……
完整代码如下
class Solution:
def findTargetSumWays(self, nums: List[int], target: int) -> int:
sum_ = sum(nums)
# 如果目标值比总和大,或者 (sum_ + target) % 2 为奇数,那么就不可能存在
if abs(target) > sum_ or (sum_ + target) % 2 == 1: return 0
bag_size = (sum_ + target) // 2
# 初始化dp
dp = [0] * (bag_size + 1)
dp[0] = 1
# 遍历
for i in range(len(nums)):
for j in range(bag_size, nums[i] - 1, -1):
dp[j] += dp[j - nums[i]]
return dp[bag_size]
518. 零钱兑换 II
题目:给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。
请你计算并返回可以凑成总金额的硬币组合数
。如果任何硬币组合都无法凑出总金额,返回 0 。
假设每一种面额的硬币有无限个
。
题目数据保证结果符合 32 位带符号整数。
👉示例 1:
输入:amount = 5, coins = [1, 2, 5]
输出:4
解释:有四种方式可以凑成总金额:
5=5
5=2+2+1
5=2+1+1+1
5=1+1+1+1+1
👉示例 2:
输入:amount = 3, coins = [2]
输出:0
解释:只用面额 2 的硬币不能凑成总金额 3 。
👉示例 3:
输入:amount = 10, coins = [10]
输出:1
题目分析——组合
组合不强调元素之间的顺序,排列强调元素之间的顺序。
本题是求组合数。
动态规划五部曲
- dp的含义
- dp[i]表示:凑成
金额为i
的组合数为dp[i-1]
- dp[i]表示:凑成
- 确定递推公式
- 组合数的递推公式不涉及
+value
dp[j] += dp[j - coins[i]]
- 组合数的递推公式不涉及
- dp数组如何初始化
- dp[0]一定要初始化为1!!!dp[0]是后续推导的起点,如果dp[0]=0,那么后续推导的结果都是0。
dp = [0] * (amount + 1)
,dp[0] = 1
- 确定遍历顺序
- 本题需要考虑顺序
- 求组合数:先遍历物品,再遍历背包
- 求排列数:先遍历背包,再遍历物品
- 举例推导dp数组
完整代码如下
class Solution:
def change(self, amount: int, coins: List[int]) -> int:
# 初始化
dp = [0] * (amount + 1)
dp[0] = 1 # 凑成金额为0的货币组合数是1
# 遍历
for i in range(len(coins)):
for j in range(coins[i], amount + 1):
dp[j] += dp[j - coins[i]]
# print(dp)
return dp[amount]
377. 中等
组合总和 Ⅳ
题目:给你一个由 不同 整数组成的
数组 nums
,和一个目标整数 target
。请你从 nums 中找出并返回总和为 target 的元素组合的个数
。
题目数据保证答案符合 32 位整数范围。
👉示例 1:
输入:nums = [1,2,3], target = 4
输出:7
解释:
所有可能的组合为:
(1, 1, 1, 1)
(1, 1, 2)
(1, 2, 1)
(1, 3)
(2, 1, 1)
(2, 2)
(3, 1)
请注意,顺序不同的序列被视作不同的组合。
👉示例 2:
输入:nums = [9], target = 3
输出:0
题目分析——排列总和数
- 给定元素是无限个可用,即:可以重复使用。
- 给定元素不一定全部使用,即:可以只使用一部分
- (1, 1, 2)和(1, 2, 1)是不同的答案,即:本题求的是
排列
数,而非组合
数。答案中元素是有序的。组合是无序的,排列是有序的。
动态规划五部曲
- dp[i]:表示
总和为i
的排列个数为dp[i]
dp[i] += dp[i - nums[j]]
- 初始化:
dp = [0] * (target + 1)
# 非0坐标要初始化为0,防止在后续遍历的时候覆盖dp的值。dp[0] = 1
# dp[0]初始化为1是没有意义的。然而其必须初始化为1!如果初始化为0,那么后续的推导就都为0了。
- 遍历顺序
- 如果求组合数,那外层for循环遍历物品,内层for循环遍历背包。
- 如果求排列数,那外层for遍历背包,内层for循环遍历物品。
- 本题是求排列数,因此:
- 外层遍历总和数(即:背包容量)
for j in range(target):
- 内层是遍历元素
for i in range(len(nums)):
- 外层遍历总和数(即:背包容量)
- 举例推导dp数组
完整代码如下
class Solution:
def combinationSum4(self, nums: List[int], target: int) -> int:
# 初始化
dp = [0] * (target + 1)
dp[0] = 1
# 遍历
for i in range(1, target + 1):
for j in nums:
if i >= j: # 保证索引不为负
dp[i] += dp[i - j]
return dp[target]
70. 爬楼梯
题目:假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。
你有多少种不同的方法可以爬到楼顶呢?
👉示例 1:
输入:n = 2
输出:2
解释:有两种方法可以爬到楼顶。
- 1 阶 + 1 阶
- 2 阶
👉示例 2:
输入:n = 3
输出:3
解释:有三种方法可以爬到楼顶。
- 1 阶 + 1 阶 + 1 阶
- 1 阶 + 2 阶
- 2 阶 + 1 阶
题目分析——排列总和数
需要n阶到达楼顶:背包容量是n
每次你可以爬 1 或 2 个台阶。
:物品为nums = [1, 2]
依旧是求排列数
动态规划五部曲
- dp[i]:表示
爬n阶台阶
共有dp[i]种方法
dp[i] += dp[i - nums[j]]
- 初始化:
dp = [0] * (n + 1)
,dp[0] = 1
- 遍历顺序:因为是排列,所以外层遍历背包,内层遍历物品
- for i in range(1, n + 1): # 注意:这里很重要,不是
range(n)
,而是range(1, n + 1)
- for j in nums: if i >= j: …
- for i in range(1, n + 1): # 注意:这里很重要,不是
- 举例说明dp[i]
完整代码如下
class Solution:
def climbStairs(self, n: int) -> int:
nums = [1, 2]
# 初始化
dp = [0] * (n + 1)
dp[0] = 1
# 遍历
for i in range(1, n + 1):
for j in nums:
if i >= j:
dp[i] += dp[i - j]
# print(dp)
return dp[n]
每次可以爬(1,m)阶,有几种方式?
class Solution:
def climbStairs(self, n: int) -> int:
# 初始化
dp = [0] * (n + 1)
dp[0] = 1
# 遍历
for i in range(1, n + 1):
for step in range(1, m + 1):
if i >= step:
dp[i] += dp[i - step]
# print(dp)
return dp[n]