「LeetCode」416-分割等和子集

119 阅读1分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 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 <= 200
  • 1 <= 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]

四、总结:

背包问题是一个很经典的问题,我们需要掌握技巧以及动态规划的相关思路,只要确定好数组的定义状态转移方程,所有的动态规划题目都能够很好的解答。