1. 题目
读题
https://leetcode.cn/problems/partition-equal-subset-sum/description/
考查点
2. 解法
思路
Leetcode 416 的问题。这道题是一个 0-1 背包问题,要求判断一个数组是否可以分成两个和相等的子集。一个可能的解法是使用动态规划,
- 算法的思想是:我们要判断一个数组能否被划分为两个和相等的子集,等价于判断数组中是否存在一个子集,它的和等于数组总和的一半。因此,我们可以定义一个目标和target为数组总和的一半,然后用动态规划的方法来求解这个问题。
- DP数组的含义是:
- dp[i][j]表示前i个数能否组成和为j的子集,它是一个布尔值,如果为true,表示可以组成,如果为false,表示不可以组成。
- DP数组的初始值是:
- dp[0][0] = true,表示没有任何数时,可以组成和为0的子集;
- dp[i][0] = true,表示前i个数都可以组成和为0的子集,只需要不选取任何数即可;
- dp[0][j] = false,表示没有任何数时,不能组成和为j(j>0)的子集。
- DP数组的状态转移公式是:对于每个数nums[i-1](注意这里的i是从1开始的),我们有两种选择,选取或不选取。如果不选取当前数,那么dp[i][j] = dp[i-1][j],表示前i个数能否组成和为j的子集,取决于前i-1个数能否组成和为j的子集;如果选取当前数,那么dp[i][j] = dp[i-1][j-nums[i-1]],表示前i个数能否组成和为j的子集,取决于前i-1个数能否组成和为j-nums[i-1]的子集。因此,我们可以得到状态转移公式:
dp[i][j] = dp[i-1][j] || dp[i-1][j-nums[i-1]]
代码逻辑
我来解释一下代码的逻辑。
首先,我们计算数组的总和 sum,并判断它是否是偶数。如果是奇数,那么直接返回 false,因为无法分成两个和相等的子集。如果是偶数,那么我们把目标和 target 设为 sum / 2,也就是每个子集的和应该达到的值。
然后,我们创建一个二维布尔数组 dp,它的大小为 (n + 1) x (target + 1),其中 n 是数组的长度。dp[i][j] 表示前 i 个元素是否可以组成和为 j 的子集。我们初始化 dp[0][0] 为 true,表示空集的和为 0。
接下来,我们遍历数组中的每个元素 nums[i - 1],并更新 dp 数组。对于每个元素,我们有两种选择:要么放入子集中,要么不放入子集中。如果放入子集中,那么 dp[i][j] 的值取决于 dp[i - 1][j - nums[i - 1]] 的值,也就是说,如果前 i - 1 个元素可以组成和为 j - nums[i - 1] 的子集,那么前 i 个元素就可以组成和为 j 的子集。如果不放入子集中,那么 dp[i][j] 的值取决于 dp[i - 1][j] 的值,也就是说,如果前 i - 1 个元素可以组成和为 j 的子集,那么前 i 个元素也可以组成和为 j 的子集。因此,我们用逻辑或运算符 || 来表示两种选择的结果。
最后,我们返回 dp[n][target] 的值,它表示整个数组是否可以分成两个和为 target 的子集。
双重循环内的逻辑。
双重循环的目的是遍历数组中的每个元素 nums[i - 1],以及每个可能的子集和 j。对于每一对 (i, j),我们要更新 dp[i][j] 的值,表示前 i 个元素是否可以组成和为 j 的子集。
我们先把 dp[i][j] 的值设为 dp[i - 1][j] 的值,也就是说,我们默认不把 nums[i - 1] 放入子集中。然后,我们判断 j 是否大于或等于 nums[i - 1],也就是说,子集的和是否可以容纳 nums[i - 1]。如果是的话,我们就有另一种选择,就是把 nums[i - 1] 放入子集中。这时,我们要看 dp[i - 1][j - nums[i - 1]] 的值,也就是说,如果前 i - 1 个元素可以组成和为 j - nums[i - 1] 的子集,那么前 i 个元素就可以组成和为 j 的子集。
因此,我们用逻辑或运算符 || 来表示两种选择的结果,也就是 dp[i][j] = dp[i][j] || dp[i - 1][j - nums[i - 1]]。这样,我们就完成了 dp[i][j] 的更新。
具体实现
class Solution { public boolean canPartition(int[] nums) { int n = nums.length; int sum = 0; for (int num : nums) { sum += num; } if (sum % 2 != 0) { return false; } int target = sum / 2; boolean[][] dp = new boolean[n + 1][target + 1]; dp[0][0] = true; for (int i = 1; i <= n; i++) { for (int j = 0; j <= target; j++) { // 默认不放入子集中,继承上一行的结果 dp[i][j] = dp[i - 1][j]; if (j >= nums[i - 1]) { // 如果可以放入子集中,考虑两种选择的结果 dp[i][j] = dp[i][j] || dp[i - 1][j - nums[i - 1]]; } } } return dp[n][target]; } }