剑指 Offer II 101. 分割等和子集(416. 分割等和子集)

题目:

 

思路:

【1】动态规划

示例输入数组{3,1,7,3}
纵坐标代表数组的下标,横坐标代表目标值的数值
[
[false, false, false, false, false, false, false, false], 
[false, false, false, false, false, false, false, false], 
[false, false, false, false, false, false, false, false], 
[false, false, false, false, false, false, false, false]
]

由于dp[i][0] = true;
假设第一个数不选,其实还是0,故为true
第二个数也不算的话,那么也还是0,所以第二行的首个也是true
以此类推
故变为:
[
[true, false, false, false, false, false, false, false], 
[true, false, false, false, false, false, false, false], 
[true, false, false, false, false, false, false, false], 
[true, false, false, false, false, false, false, false]
]

当 i==0 时,只有一个正整数 nums[0] 可以被选取,
因为如果不选的时候0已经为true了,所以选的时候值就会为3,故值为3的时候也为true
因此 dp[0][nums[0]]=true。
[
[true, false, false, true, false, false, false, false], 
[true, false, false, false, false, false, false, false], 
[true, false, false, false, false, false, false, false], 
[true, false, false, false, false, false, false, false]
]

当 i==1 时,正整数 nums[1] 可以被选取,
如果不选取那么其实就需要复制nums[0]的情况
如果选取了那么对应就要叠加,如在nums[0]的时候0时true,那么nums[1]的1值位置就要为true(因为0+1=1)
所以在nums[0]的时候3时true,那么nums[1]的4值位置就要为true(因为3+1=4)
[
[true, false, false, true, false, false, false, false], 
[true, true, false, true, true, false, false, false], 
[true, false, false, false, false, false, false, false], 
[true, false, false, false, false, false, false, false]
]

当 i==2 时,正整数 nums[2] 可以被选取(值为:7),
[
[true, false, false, true, false, false, false, false], 
[true, true, false, true, true, false, false, false], 
[true, true, false, true, true, false, false, true], 
[true, false, false, false, false, false, false, false]
]
当 i==3 时,正整数 nums[3] 可以被选取(值为:3),
[
[true, false, false, true, false, false, false, false], 
[true, true, false, true, true, false, false, false], 
[true, true, false, true, true, false, false, true], 
[true, true, false, true, true, false, true, true]
]

代码展示:

//时间31 ms击败47.71%
//内存45.8 MB击败13.24%
//可以看做是0-1背包问题,对于该元素的选着与不选择问题
class Solution {
    public boolean canPartition(int[] nums) {
        int n = nums.length;
        if (n < 2) {
            return false;
        }
        int sum = 0, maxNum = 0;
        for (int num : nums) {
            sum += num;
            maxNum = Math.max(maxNum, num);
        }
        if (sum % 2 != 0) {
            return false;
        }
        int target = sum / 2;
        if (maxNum > target) {
            return false;
        }
        boolean[][] dp = new boolean[n][target + 1];
        for (int i = 0; i < n; i++) {
            dp[i][0] = true;
        }
        dp[0][nums[0]] = true;
        for (int i = 1; i < n; i++) {
            int num = nums[i];
            for (int j = 1; j <= target; j++) {
                if (j >= num) {
                    dp[i][j] = dp[i - 1][j] | dp[i - 1][j - num];
                } else {
                    dp[i][j] = dp[i - 1][j];
                }
            }
        }
        return dp[n - 1][target];
    }
}

//时间23 ms击败89.64%
//内存55.3 MB击败11.28%
//利用4字节的int类型替换8字节的boolean类型,即节约空间,也加快运行速度
class Solution {
    public boolean canPartition(int[] nums) {
        int n = nums.length;
        //如果个数是1,那么其实分不到两个数组
        if (n < 2)  return false;

        int sum = 0, maxNum = 0;
        for (int num : nums) {
            sum += num;
            maxNum = Math.max(maxNum, num);
        }
        //如果总数是奇数,其实也是平分不了的
        if (sum % 2 != 0)  return false;
        int target = sum / 2;
        //而且如果存在某个值数值占总额一半以上那么,就不存在平分两个数组的情况:如【11】【1,3,5】
        if (maxNum > target) return false;

        int[][] dp = new int[n][target + 1];
        //数值为0的情况默认是肯定存在的
        for (int i = 0; i < n; i++) {
            dp[i][0] = 1;
        }
        //由于这是第一个数,数值为0的情况代表不选,那么现在就要决定选的情况
        dp[0][nums[0]] = 1;
        for (int i = 1; i < n; i++) {
            int num = nums[i];
            for (int j = 1; j <= target; j++) {
                if (j >= num) {
                    //能选的情况才判断是否可以叠加
                    dp[i][j] = dp[i - 1][j] | dp[i - 1][j - num];
                } else {
                    //在不能选的情况下只能复制上面
                    dp[i][j] = dp[i - 1][j];
                }
            }
        }
        return dp[n - 1][target] == 1;
    }
}

//时间18 ms击败97.54%
//内存40.3 MB击败94.22%
//时间复杂度:O(n×target),其中 n 是数组的长度,target 是整个数组的元素和的一半。
//需要计算出所有的状态,每个状态在进行转移时的时间复杂度为 O(1)。
//空间复杂度:O(target),其中 target 是整个数组的元素和的一半。
//空间复杂度取决于 dp 数组,在不进行空间优化的情况下,空间复杂度是 O(n×target),在进行空间优化的情况下,空间复杂度可以降到 O(target)。

//将二维数组转为一维数组
class Solution {
    public boolean canPartition(int[] nums) {
        int n = nums.length;
        //如果个数是1,那么其实分不到两个数组
        if (n < 2)  return false;

        int sum = 0, maxNum = 0;
        for (int num : nums) {
            sum += num;
            maxNum = Math.max(maxNum, num);
        }
        //如果总数是奇数,其实也是平分不了的
        if (sum % 2 != 0)  return false;
        int target = sum / 2;
        //而且如果存在某个值数值占总额一半以上那么,就不存在平分两个数组的情况:如【11】【1,3,5】
        if (maxNum > target) return false;

        //将二维数组变为一维数组,因为按照二维数组,每一次大多数也是将上一次的结果复制到下一次中,并且有选的情况才会进行修改
        int[] dp = new int[target + 1];
        //默认数值为0的情况会出现
        dp[0] = 1;
        for (int i = 0; i < n; i++) {
            int num = nums[i];
            for (int j = target; j >= num; --j) {
                dp[j] |= dp[j - num];
            }
        }
        return dp[target] == 1;
    }
}

 

posted @ 2023-05-16 17:05  忧愁的chafry  阅读(25)  评论(0编辑  收藏  举报