剑指 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; } }