导语
leetcode刷题笔记记录,本篇博客是贪心部分的第二期,主要记录题目包括:
Leetcode 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
提示:
1 <= nums.length <= 200 <= nums[i] <= 10000 <= sum(nums[i]) <= 1000-1000 <= target <= 1000
解法
这个问题实际上转化为了一个背包问题。问题的核心是,给定的nums中有多少种方式,使得其中的数加起来等于(target + sum(nums)) // 2。为什么要这样转化呢?因为每个数要么被加,要么被减。如果我们让它加,那就相当于把这个数放进“背包”里。所以,问题就转化为了:有多少种方法可以从nums中选择一些数,使其和为(target + sum(nums)) // 2。
动规五部曲:
- dp数组含义:dp[i]表示有多少种方法从nums中选出一些数,使其和为i
- 递推公式:dp[j] += dp[j-nums[i]]
- 初始化:dp[0]=1,其他为0
- 遍历顺序,与0-1背包的一维滚动数组数组的顺序一致;
- 打印数组:略
class Solution:
def findTargetSumWays(self, nums: List[int], target: int) -> int:
# 计算nums的总和
total = sum(nums)
# 首先检查是否可能达到目标
# 如果 (total + target) 是奇数,那么不可能达到目标
# 同样,如果目标的绝对值大于总和,那么也不可能达到目标
if (total + target) % 2 != 0 or abs(target) > total:
return 0
# 计算新的目标:这是我们要从nums中选出的数的总和
positive = (total + target) // 2
# 初始化dp数组,dp[i]表示有多少种方法从nums中选出一些数,使其和为i
dp = [0] * (positive + 1)
dp[0] = 1 # 选择0个数,使其和为0的方法只有1种
# 遍历nums中的每个数
for i in range(len(nums)):
# 从positive开始,逆向到nums[i]
# 为每个j,考虑是否使用nums[i]来达到目标
for j in range(positive, nums[i]-1, -1):
# 更新dp[j]:不使用nums[i]的方法数 + 使用nums[i]的方法数
dp[j] += dp[j-nums[i]]
# dp[positive]就是我们的答案:从nums中选择一些数,使其和为positive的方法数
return dp[positive]
Leetcode 474. 一和零
题目描述
给你一个二进制字符串数组 strs 和两个整数 m 和 n 。
请你找出并返回 strs 的最大子集的长度,该子集中 最多 有 m 个 0 和 n 个 1 。
如果 x 的所有元素也是 y 的元素,集合 x 是集合 y 的 子集 。
示例 1:
输入: strs = ["10", "0001", "111001", "1", "0"], m = 5, n = 3
输出: 4
解释: 最多有 5 个 0 和 3 个 1 的最大子集是 {"10","0001","1","0"} ,因此答案是 4 。
其他满足题意但较小的子集包括 {"0001","1"} 和 {"10","1","0"} 。{"111001"} 不满足题意,因为它含 4 个 1 ,大于 n 的值 3 。
示例 2:
输入: strs = ["10", "0", "1"], m = 1, n = 1
输出: 2
解释: 最大的子集是 {"0", "1"} ,所以答案是 2 。
提示:
1 <= strs.length <= 6001 <= strs[i].length <= 100strs[i]仅由'0'和'1'组成1 <= m, n <= 100
解法
这个问题实际上是一个多维的0-1背包问题,其中两个维度分别是m(0的最大数量)和n(1的最大数量)。使用动规五部曲:
- dp数组含义:这里由于背包容量是二维的,所以定义一个二维dp数组,其表示装满i个0,j个1的背包,最多背了dp[i][j]个物品;
- 递推公式:回顾0-1背包的递推公式为: ,这里的二维dp数组递推公式为:
- 初始化:考虑其代表含义,dp[0][0]应表示背包容量为0,0时的物品个数,那么肯定为0,由于递推公式中的max操作,所以非零下标也初识为0;
- 遍历顺序:先物品,再背包,且背包倒序;
- 打印dp数组
这里需要注意的一点是x,y的初始化。应该在选择一个字符串后进行初始化。完整代码如下:
class Solution:
def findMaxForm(self, strs: List[str], m: int, n: int) -> int:
# 初始化二维dp数组,其中dp[i][j]表示有i个0和j个1时最多可以组成的子集数
dp = [[0] * (n+1) for _ in range(m+1)]
# 遍历输入字符串的列表
for string in strs:
x, y = 0, 0 # x和y用于存储当前字符串中0和1的数量
# 统计当前字符串中0和1的数量
for char in string:
if char == "0":
x += 1
else:
y += 1
# 更新dp数组
# 从右下角开始更新,这样可以确保同一个字符串不会被多次使用
for i in range(m, x - 1, -1):
for j in range(n, y - 1, -1):
# dp[i][j]要么是自己(不使用当前字符串),要么是dp[i-x][j-y] + 1(使用当前字符串)
dp[i][j] = max(dp[i][j], dp[i - x][j - y] + 1)
# 最后的dp[m][n]就是问题的答案
return dp[m][n]