image.png
问题分析:除了直接利用01背包的思想来做(即从nums[] 中寻找元素,判断其元素和是否等于 nums[]所有元素的一半),我们还通过微调dp方程的定义,直接得到转移状态。
状态定义dp[i][j]表示考虑前 i 个数字,是否可以恰好填充满背包容量为 j 的背包。
状态初始化:我们对状态方程的定义做了下标偏移,所以对于i=0的情况,我们可以这样理解,在不考虑任何数字的前提下,填充满容量为0的背包是完全有可能的,其他情况均为不可能,
即:dp[0][0]=true, dp[0][1, ...]=false
状态转移dp[i][j] = dp[i - 1][j] || dp[i - 1][j - nums[i - 1]] if j >=nums[i-1]

解法一(最朴素)

  1. class Solution {
  2. public boolean canPartition(int[] nums) {
  3. int sum = 0;
  4. for(int num : nums){
  5. sum += num;
  6. }
  7. if(sum % 2 != 0){ //如果nums中元素总和为奇数,那么不可能划分为两个总和相当的子集
  8. return false;
  9. }
  10. int n = nums.length;
  11. boolean[][] dp = new boolean[n + 1][sum / 2 + 1];
  12. dp[0][0] = true;
  13. for(int i = 1; i <= n; i++){
  14. int val = nums[i - 1];
  15. for(int j = 0; j <= sum / 2; j++){
  16. boolean a = dp[i-1][j];
  17. boolean b = j - val >= 0 ? dp[i - 1][j - val] : false;
  18. dp[i][j] = a || b;
  19. }
  20. }
  21. return dp[n][sum / 2];
  22. }
  23. }

解法二(一维)

  1. class Solution {
  2. public boolean canPartition(int[] nums) {
  3. int sum = 0;
  4. for(int num : nums){
  5. sum += num;
  6. }
  7. if(sum % 2 != 0){
  8. return false;
  9. }
  10. int n = nums.length;
  11. boolean[] dp = new boolean[sum / 2 + 1];
  12. dp[0] = true;
  13. for(int i = 1; i <= n; i++){
  14. int val = nums[i - 1];
  15. for(int j = sum / 2; j >= val; j--){
  16. dp[j] |= dp[j - val];
  17. }
  18. }
  19. return dp[sum / 2];
  20. }
  21. }