动态规划:子数组的和

    给定一个正整数数组,问能否把其分成两个子数组,使这两个子数组的和相等。

    分析:先把数组和sum求出来,题目实际上是求是否存在子数组,使该子数组的和等于sum的一半。

    方法一:用sum的一半,不断减去数组中元素,把所有可能的差值算出来,看是否有等于0。该方法时间复杂度和空间复杂度较高。

    方法二:递增法,分别算出子数组长度分别为1,2 ... n 时,看子数组的和是否有等于sum的一半的情况。

    方法三:作为0/1背包问题求解。用 dp[i][j] 表示前 i 个元素能否得到和 j。如果用数组的前 i 个元素能得到和为 j , 则 dp[i][j] 为true,否则为false。

    初始条件:dp[i][0] = true,即任意 i 都可以得到和为0的情况;且 dp[0][j] = false(j = 1...n ),因为元素都为正数。

    转换过程:对于元素 i ,如果不加该元素,则dp[i][j] = dp[i - 1][j],即如果前i-1个元素能得到j,那么前i个元素也可以;如果加上该元素,则 dp[i][j] = dp[i-1][j-nums[i]],即如果前 i-1 个元素可以得到 j-nums[i],那么加上 nums[i] 就可以得到 j。

public boolean canPartition(int[] nums) {
    int sum = 0;
    for (int num : nums) {
        sum += num;
    }
    if ((sum & 1) == 1) {
        return false;
    }
    sum /= 2;
    int n = nums.length;
    boolean[][] dp = new boolean[n+1][sum+1];
    for (int i = 0; i < dp.length; i++) {
        Arrays.fill(dp[i], false);
    }
    
    dp[0][0] = true;
    
    for (int i = 1; i < n+1; i++) {
        dp[i][0] = true;
    }
    for (int j = 1; j < sum+1; j++) {
        dp[0][j] = false;
    }
    
    for (int i = 1; i < n+1; i++) {
        for (int j = 1; j < sum+1; 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][sum];
}

    还可以进一步对空间进行优化:

public boolean canPartition(int[] nums) {
    int sum = 0;
    
    for (int num : nums) {
        sum += num;
    }
    
    if ((sum & 1) == 1) {
        return false;
    }
    sum /= 2;
    
    int n = nums.length;
    boolean[] dp = new boolean[sum+1];
    Arrays.fill(dp, false);
    dp[0] = true;
    
    for (int num : nums) {
        for (int i = sum; i > 0; i--) {
            if (i >= num) {
                dp[i] = dp[i] || dp[i-num];
            }
        }
    }
    
    return dp[sum];
}

猜你喜欢

转载自blog.csdn.net/xiezongsheng1990/article/details/79997073
今日推荐