动态规划-01背包问题
本篇中将介绍几道01背包类型的问题。
分割等和子集
416. 分割等和子集 - 力扣(LeetCode)
题目描述
给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
示例 1:
输入:nums = [1,5,11,5]
输出:true
解释:数组可以分割成 [1, 5, 5] 和 [11] 。
示例 2:
输入:nums = [1,2,3,5,2]
输出:false
解释:数组不能分割成两个元素和相等的子集。
我们可以将这个问题视为一个典型的“背包问题”的变种,具体来说是“0-1背包问题”的一个特殊应用。不过,在这个问题中,我们不是要求达到某个特定重量的最大价值,而是要求判断是否存在一种方式,将数组中的元素分为两组,使得两组的和相等。
问题分析
-
目标和:首先,我们需要计算数组
nums
中所有元素的总和totalSum
。如果totalSum
是奇数,那么显然无法将数组分割成两个和相等的子集,因为两个相等的和加起来必须是偶数。 -
动态规划数组:如果
totalSum
是偶数,我们定义target = totalSum / 2
。接下来,我们使用动态规划来判断是否存在一个子集的和为target
。我们定义一个布尔型动态规划数组dp
,其中dp[i]
表示是否存在一个子集,其和为i
。注意,这里的i
的取值范围是0
到target
(包含)。 -
状态转移方程:
- 初始化:
dp[0] = true
(空集的和为0)。 - 对于数组
nums
中的每个元素num
,以及每个可能的和j
(从target
开始向下遍历到num
),如果dp[j-num]
为真(即存在一个子集的和为j-num
),则我们可以将num
加到这个子集中,使得子集的和变为j
,因此设置dp[j] = true
。
- 初始化:
-
结果判断:如果
dp[target]
为真,则说明存在一个子集的和为target
,即可以将数组nums
分割成两个和相等的子集。
bool canPartition(vector<int>& nums) { int totalSum = 0; for (int num : nums) { totalSum += num; } // 如果总和是奇数,则无法分割成两个和相等的子集 if (totalSum % 2 != 0) { return false; } int target = totalSum / 2; vector<bool> dp(target + 1, false); dp[0] = true; // 空集的和为0 for (int num : nums) { for (int j = target; j >= num; --j) { if (dp[j - num]) { dp[j] = true; } } } return dp[target];
}
目标和
494. 目标和 - 力扣(LeetCode)
题目描述
给你一个非负整数数组 nums 和一个整数 target 。 向数组中的每个整数前添加 '+' 或 '-' ,然后串联起所有整数,可以构造一个 表达式 。返回可以通过上述方法构造的、运算结果等于 target 的不同 表达式 的数目。
例如,nums = [3, 1] ,可以在 2 之前添加 '+' ,在 1 之前添加 '-' ,然后串联起来得到表达式 "+3-1" 以及"+3+1"等表达式。
解题思路
将数组分成两份一部分加上正号,另一部分加上负号,加正号的那一部分总和为a,另一部分总和绝对值为b
题目要求即要使a - b = target;
并且a + b = sum;
所以题目要求就是找出数组中 和为 : (target+sum)/2 的所有组合数。实际上就是01背包问题。
我们可以定义一个二维数组 dp[i][j]
来表示在前 i
个数字中选取一些数字(每个数字前面可以添加 '+' 或 '-'),使得这些数字的和为 j
的不同表达式的数目。
int findTargetSumWays(vector<int>& nums, int target) {int n = nums.size();int sum = 0;for (int i = 0; i < n; i++) {sum += nums[i];}if ((target + sum) % 2 == 1)return false;int m = (target + sum) / 2;if (m < 0)return 0;vector<vector<int>> dp(n + 1, vector<int>(m + 1, 0));// dp[i][j]: 前i个数 总和为j 的所有方法数目for (int i = 0; i <= n; i++) { // 前i个数 什么都不选总和为0dp[i][0] = 1;}for (int i = 1; i <= n; i++) {for (int j = 0; j <= m; j++) {if (j - nums[i - 1] >= 0) {dp[i][j] = dp[i - 1][j - nums[i - 1]] + dp[i - 1][j];} else {dp[i][j] = dp[i - 1][j];}}}return dp[n][m];}