Go&Java算法之分割等和子集

127 阅读1分钟

这是我参与2022首次更文挑战的第23天,活动详情查看:2022首次更文挑战

分割等和子集

算法一:动态规划(Java)

这道题可以换一种表述:给定一个只包含正整数的非空数组 nums[0],判断是否可以从数组中选出一些数字,使得这些数字的和等于整个数组的元素和的一半。因此这个问题可以转换成「0-10−1 背包问题」。这道题与传统的「0-10−1 背包问题」的区别在于,传统的「0-10−1 背包问题」要求选取的物品的重量之和不能超过背包的总容量,这道题则要求选取的数字的和恰好等于整个数组的元素和的一半。类似于传统的「0-10−1 背包问题」,可以使用动态规划求解。

  • 先遍历物品,然后遍历背包重量
    • weight数组的大小 就是物品个数
for(int i = 1; i < weight.size(); i++) { // 遍历物品
    for(int j = 0; j <= bagWeight; j++) { // 遍历背包容量
        if (j < weight[i]) dp[i][j] = dp[i - 1][j]; // 这个是为了展现dp数组里元素的变化
        else dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);

    }
}
  • 先遍历背包,再遍历物品
    • weight数组的大小 就是物品个数
for(int j = 0; j <= bagWeight; j++) { // 遍历背包容量
    for(int i = 1; i < weight.size(); i++) { // 遍历物品
        if (j < weight[i]) dp[i][j] = dp[i - 1][j];
        else dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
    }
}
  • 每次递归,都有两个选择:

    • 选nums[i]。基于选它,往下继续选(递归):dfs(curSum + nums[i], i + 1)
    • 不选nums[i]。基于不选它,往下继续选(递归):dfs(curSum, i + 1)
  • 递归的终止条件有三种情况:

    • curSum > target,已经爆了,不用继续选数字了,终止递归,返回false。
    • curSum == target,满足条件,不用继续选数字了,终止递归,返回true。
    • 指针越界,考察完所有元素,能走到这步说明始终没有返回true,所以返回false。
class Solution {
    public boolean canPartition(int[] nums) {
        if (nums == null || nums.length == 0) {
            return false;
        }

        int sum = 0;
        for (int i = 0; i < nums.length; i++) {
            sum += nums[i];
        }

        if (sum % 2 != 0) {
            return false;
        }
        int target = sum / 2;

        boolean[] dp = new boolean[target + 1];

        for (int i = 0; i < nums.length; i++) {
            for (int j = target; nums[i] <= j; j--) {
                if (dp[target]) {
                    return true;
                }

                if (nums[i] == j) {
                    dp[j] = true;
                    continue;
                }
                dp[j] = dp[j] || dp[j - nums[i]];
            }
        }

        return dp[target];
    }
}

时间复杂度:O(N * target)

空间复杂度:O(target)

算法一:动态规划(Go)

思路同上

func canPartition(nums []int) bool {
	sum := 0
	for _, n := range nums {
		sum += n
	}
	if sum%2 == 1 {
		return false
	}
	target := sum / 2

	var dfs func(curSum, i int) bool
	dfs = func(curSum, i int) bool {
		if i == len(nums) || curSum > target {
			return false
		}
		if curSum == target {
			return true
		}
		return dfs(curSum+nums[i], i+1) || dfs(curSum, i+1)
	}
	return dfs(0, 0)
}

时间复杂度:O(N * target)

空间复杂度:O(target)