LeetCode416、分割等和子集(转化为0-1背包问题)

题目描述

https://leetcode-cn.com/problems/partition-equal-subset-sum/
在这里插入图片描述

解法(背包类型dp问题)

官方题解开篇就是:
在这里插入图片描述
还是老老实实做题吧:
这是背包型dp问题,

  • 思考当前的问题里面,要描述的变量有哪些,比如物品数量,背包重量,是否能装满,也就是我们第一步要做的:要明确两点,「状态」和「选择」,它不同于子序列和字符串的问题了,而是描述一个子问题现象,对应的下标如dp[i][j]也不再是从s[i…j]的什么什么,而是i个物品里面选择装什么,能把容量为j的背包装满或达到最大价值。
  • 所以我们第二步要做的:要明确dp数组的定义。
  • 最后,第三步,根据「选择」,思考状态转移的逻辑。
class Solution {
    
    
    public boolean canPartition(int[] nums) {
    
    
        if(nums==null ||nums.length==0||nums.length==1) return false;
        int sum = 0;
        for(int i=0;i<nums.length;i++){
    
    
            sum+=nums[i];
        }
        if(sum%2!=0){
    
    //总和不是偶数,则不能划分两个子集元素和
            return false;
        }
        sum = sum/2;///寻找一个子数组的和为sum即可
        
        //定义dp数组----转化为0-1背包问题,在给定的N个物品里面,背包的重量恰好为sum,我们要装物品,恰好能装满背包
        boolean [][] dp = new boolean[nums.length+1][sum+1];//dp[i][j]=x表示给定的前i个物品,背包重量为j,是否能够恰好装满的结果

        //边界:dp[0][...]的值为false,表示没有物品装
        //dp[...][0]值为false,表示要装的重量为0,不装就是true
        //-------------------------------默认初始化就是false
         for(int i=0;i<nums.length;i++){
    
    
             dp[i][0] = true;//
         }
         int n = nums.length;

        //递推 已经知道了dp[i][j],----num[i]表示物品i的重量
        for (int i = 1; i <= n; i++) {
    
    //由于要求dp[i][j],要用到dp[i-1][j]所以两个维度都是从低到高
        for (int j = 1; j <= sum; j++) {
    
    
            if (j - nums[i - 1] < 0) {
    
    //第i个物品的重量为nums[i-1]
               // 背包容量不足,不能装入第 i 个物品
                dp[i][j] = dp[i - 1][j]; //不能装入,则背包重量还是j
            } else {
    
    
                // 装入或不装入背包  装入,则重量为j,上一个的要装的重量也为j
                //不装入,则现在dp[i][j] = 去掉要装的nums[i-1]
                dp[i][j] = dp[i - 1][j] | dp[i - 1][j-nums[i-1]];
            }
        }
    }
        return dp[n][sum];//n个物品,背包容量为sum,是否能恰好装满
    }
}

在这里插入图片描述

从我们写出的代码里面,我们可以知道递推的值与相邻的几个状态有关,所以我们可以使用状态压缩来缩小空间复杂度。

class Solution {
    
    
    public boolean canPartition(int[] nums) {
    
    
        if(nums==null ||nums.length==0||nums.length==1) return false;
        int sum = 0;
        for(int i=0;i<nums.length;i++){
    
    
            sum+=nums[i];
        }
        if(sum%2!=0){
    
    //总和不是偶数,则不能划分两个子集元素和
            return false;
        }
        sum = sum/2;///寻找一个子数组的和为sum即可
        boolean [] dp = new boolean[sum+1];//dp[i][j]=x表示给定的前i个物品,背包重量为j,是否能够恰好装满的结果

    
        dp[0]=true;
        int n = nums.length;
        for (int i = 0; i <n; i++) {
    
    //由于要求dp[i][j],要用到dp[i-1][j]所以两个维度都是从低到高
        for (int j = sum;j>=0 ; j--) {
    
    //因为使用了状态压缩,所以之前的dp[j]就是上一次的dp[i-1][j],现在的dp[j]就是dp[i][j];
        //j应该从后往前反向遍历,因为每个物品(或者说数字)只能用一次,以免之前的结果影响其他的结果。
            if (j - nums[i] >= 0) {
    
    //能装的前提下,装或者不装,不能装,则其值还是dp[j] = dp[j],所以这里直接省去
                dp[j] = dp[j] | dp[j-nums[i]];
            }
        }
    }
        return dp[sum];//n个物品,背包容量为sum,是否能恰好装满
    }
}

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_44861675/article/details/114552557