一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第21天,点击查看活动详情。
一.题目:
416. 分割等和子集 给你一个 只包含正整数 的 非空 数组
nums。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
示例 1:
输入: nums = [1,5,11,5]
输出: true
解释: 数组可以分割成 [1, 5, 5] 和 [11] 。
示例 2:
输入: nums = [1,2,3,5]
输出: false
解释: 数组不能分割成两个元素和相等的子集。
提示:
1 <= nums.length <= 2001 <= nums[i] <= 100
二、思路分析:
首先,这道题目我们看着觉得可以使用回溯的方法进行求解,但是发现会造成超时。随后想到背包问题的类似场景,我们把数组想象成是背包,数组内的元素就是一个物品,我们只需要选择装还是不装它就能够得到最终的结果。思路如下:
- 我们转换成背包思想,要求的背包重量就是数组总值除以
2,如果不是整数直接返回false,因为不管怎么分都不能形成二等分。 - 确定状态方程,因为只有装进背包和不装进背包两个状态,所以状态转移方程
dp[i][j] = dp[i - 1][j] || dp[i - 1][j - nums[i - 1]]。 - 初始化把
dp[i][0]全部置为true。 - 结果返回
dp[N][M]即可,代表在N个元素中找到是否能够正好装的重量达到M的条件。 这道题目也有优化的点,因为我们的状态转移方程当前的索引只与前一个状态有关,所以我们可以取压缩数组取得更小的时间复杂度,优化版本也写在了代码里面。
三、代码:
/**
* @param {number[]} nums
* @return {boolean}
*/
var canPartition = function (nums) {
//转换为背包问题
//定义dp[N][M]:重量为M数量为N能否装满
let N = nums.length
let M = nums.reduce((pre, cur) => {
return pre + cur
}, 0)
let dp = Array.from(new Array(N + 1), () => new Array(M + 1).fill(false))
if (M % 2 != 0) {
return false
}
M = M / 2
for (let i = 0; i < N; i++) {
dp[i][0] = true
}
for (let i = 1; i <= N; i++) {
//状态转移方程
//第i个元素装或不装
for (let j = 0; j <= M; j++) {
// 背包容量不足
if (j < nums[i - 1]) {
dp[i][j] = dp[i - 1][j]
} else {
dp[i][j] = dp[i - 1][j] || dp[i - 1][j - nums[i - 1]]
}
}
}
return dp[N][M]
};
//优化版本
if( M % 2 != 0 ) {
return false
}
M = M/2
let dp = new Array(M+1).fill(false)
dp[0] = true
for (let i = 0; i < N; i++) {
//状态转移方程
//第i个元素装或不装
for (let j = M; j >= 0; j--) {
if (j-nums[i]>= 0) {
dp[j] = dp[j] || dp[j-nums[i]]
}
}
}
return dp[M]
四、总结:
背包问题是一个很经典的问题,我们需要掌握技巧以及
动态规划的相关思路,只要确定好数组的定义和状态转移方程,所有的动态规划题目都能够很好的解答。