LeetCode Top 100 Liked Questions 416. Partition Equal Subset Sum (Java版; Medium)

welcome to my blog

LeetCode Top 100 Liked Questions 416. Partition Equal Subset Sum (Java版; Medium)

题目描述
Given a non-empty array containing only positive integers, find if the array can be partitioned into two
subsets such that the sum of elements in both subsets is equal.

Note:

Each of the array element will not exceed 100.
The array size will not exceed 200.


Example 1:

Input: [1, 5, 11, 5]

Output: true

Explanation: The array can be partitioned as [1, 5, 5] and [11].


Example 2:

Input: [1, 2, 3, 5]

Output: false

Explanation: The array cannot be partitioned into equal sum subsets.
01背包: 行是物品, 列是容量
class Solution {
    public boolean canPartition(int[] nums) {
        int sum = 0;
        for(int a : nums){
            sum += a;
        }
        if((sum&1)==1){
            return false;
        }
        sum = sum/2;
        //01背包
        //创建二维状态数组,行:物品索引,列:容量(包括 0)
        //dp[i][j]表示nums[0,i]中每个元素最多使用一次, 是否可以拼凑出目标金额j
        //dp[i][j]表示nums[0,i]以nums[i]结尾时, 是否可以拼凑出目标金额j   选或者不选nums[i], 这样就能限制每个元素最多使用一次
        //dp[i][j] = dp[i-1][j]  不选nums[i]
        //dp[i][j] = dp[i-1][j-nums[i]]  选nums[i]
        
        boolean[][] dp = new boolean[nums.length][sum+1];
        //初始化第一行
        if(nums[0]<=sum){
            dp[0][nums[0]] = true;
        }
        //初始化第一列
        for(int i=0; i<nums.length; i++){
            dp[i][0] = true;
        }
        for(int i=1; i<nums.length; i++){
            for(int j=1; j<sum+1; j++){
                dp[i][j] = dp[i-1][j];
                if(j-nums[i]>=0){
                    dp[i][j] = dp[i][j] || dp[i-1][j-nums[i]];
                }
            }
        }
        return dp[nums.length-1][sum];

    }
}

第一次做; 背包问题; 优化二维DP的空间复杂度, 优化成一维; 核心:普通的二维dp中, dp[i][j]只依赖它正上方的元素,和正上方左边的某个元素; 优化后仍是两层for循环, 第二层for循环要从后往前遍历, 因为前面的状态不会依赖后面的状态, 所以改变后面的状态没有影响, 但是绝对不能从前往后遍历; 先把普通的dp掌握牢固
/*
优化dp的空间复杂度
*/
class Solution {
    public boolean canPartition(int[] nums) {
        int sum = 0;
        for(int a: nums)
            sum += a;
        //sum必须是偶数,否则返回false
        if(sum%2==1)
            return false;
        //
        int target = sum>>1;
        boolean[] dp = new boolean[target+1];
        //初始化
        dp[0]=true;
        for(int j=1; j<=target; j++){
            if(nums[0]==j) //dp[j] = nums[0]==j
                dp[j]=true;
        }
        //dp主体
        for(int i=1; i<nums.length; i++){
            //核心:从后往前遍历
            for(int j=target; j>=1; j--){
                //不选nums[i]; 不用执行操作
                //
                //选nums[i]
                if(j>=nums[i])
                    dp[j] = dp[j] || dp[j-nums[i]];
            }
        }
        return dp[target];
    }
}
坚持画图

第一次做; 背包问题; 为什么这道题没法直接从暴利递归转成dp? 为什么这道题没法通过memo[]数组优化暴利递归?
  • 时间复杂度O(N^2)
  • 空间复杂度O(N)
/*
dp[i][j]:表示从数组的 [0, i] 这个区间内挑选一些正整数,每个数只能用一次,能否使这些数的和等于 j。

dp[i][j] means whether the specific sum j can be gotten from the first i numbers

上面上个解释不同, 个人倾向第一种解释, 但是第二种解释也很清晰

为什么?
这道题没法直接从暴利递归转成dp
这道题没法通过memo[]数组优化暴利递归
*/
import java.util.Arrays;


class Solution {
    public boolean canPartition(int[] nums) {
        int sum = 0;
        for(int a:nums)
            sum += a;
        //sum必须是偶数; 因为数组内的元素都是整数, 两个相同的整数相加一定是偶数
        if(sum%2==1)
            return false;
        int target = sum>>1;
        int n = nums.length;
        boolean[][] dp = new boolean[n][target+1];
        //初始化
        for(boolean[] d : dp)
            Arrays.fill(d, false);
        dp[0][0] = true;
        //第0列
        for(int i=0; i<n; i++)
            dp[i][0] = true;
        //第0行
        for(int j=1; j<=target; j++){
            if(nums[0] == j)
                dp[0][j]=true;
        }
        //dp主体
        for(int i=1; i<n; i++){
            for(int j=1; j<=target; j++){
                //不选nums[i]
                dp[i][j] = dp[i-1][j];
                //选nums[i]
                if(j>=nums[i])
                    dp[i][j] = dp[i-1][j] || dp[i-1][j-nums[i]];
            }
        }
        //
        return dp[n-1][target];
        
    }
}
第一次做; 对于每个位置:选或者不选; 递归终止条件:1)index==nums.length; 2)currSum*2==sum;递归函数逻辑:选或者不选当前元素,然后新条件新递归; 超时:28 / 105
/*
我的第一反应: 尝试各种可能, 针对每个位置的操作:选或不选
题解说这是背包问题, 那么什么是背包问题?
*/
class Solution {
    public boolean canPartition(int[] nums) {
        //ensure a non-empty arr
        //
        int sum = 0;
        for(int i=0; i<nums.length; i++){
            sum += nums[i];
        }
        return core(nums, 0, 0, sum);       
    }
    
    public boolean core(int[] nums, int index, int currSum, int sum){
        //base case
        if(index==nums.length)
            return false;
        if(currSum<<1 == sum)
            return true;
        //对于当前位置index, 选或者不选
        boolean res = false;
        //不选
        res = core(nums, index+1, currSum, sum);
        if(res==true)
            return true;
        //选
        res = core(nums, index+1, currSum+nums[index], sum);
        return res;
    }
}
LeetCode最优解
This problem is essentially let us to find whether there are several numbers in a set which are able to sum to a specific 
value (in this problem, the value is sum/2).

Actually, this is a 0/1 knapsack problem, for each number, we can pick it or not. Let us assume dp[i][j] means whether the 
specific sum j can begotten from the first i numbers. If we can pick such a series of numbers from 0-i whose sum is j, 
dp[i][j] is true, otherwise it is false.

Base case: dp[0][0] is true; (zero number consists of sum 0 is true)

Transition function: For each number, if we don't pick it, dp[i][j] = dp[i-1][j], which means if the first i-1 elements 
has made it to j, dp[i][j] would also make it to j (we can just ignore nums[i]). If we pick nums[i]. dp[i][j] = dp[i-1][j-nums[i]], 
which represents that j is composed of the current value nums[i] and the remaining composed of other previous numbers. 
Thus, the transition function is dp[i][j] = dp[i-1][j] || dp[i-1][j-nums[i]]

talking is cheap:
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];
}
But can we optimize it? It seems that we cannot optimize it in time. But we can optimize in space. We currently use two dimensional array to solve it, but we can only use one dimensional array.

So the code becomes:
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];
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值