lotus

贵有恒何必三更眠五更起 最无益只怕一日曝十日寒

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

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];
    }
}

  

 

3. 总结

posted on 2023-05-01 16:46  白露~  阅读(31)  评论(0编辑  收藏  举报