【随想录12】01背包练习

本文详细介绍了动态规划在解决01背包问题中的应用,包括分割等和子集、最后一块石头的重量II、目标和以及一和零等经典题目。通过分析和比较不同解法,如暴力搜索、记忆化搜索和动态规划,阐述了如何优化算法,减少计算复杂度,实现高效求解。同时,展示了动态规划的一维优化技巧,提高了解题效率。
摘要由CSDN通过智能技术生成

动态规划01背包练习

01背包的题目:

416. 分割等和子集

1049. 最后一块石头的重量 II

494. 目标和

474. 一和零


416. 分割等和子集

在这里插入图片描述
类加所有数字为sum,如果sum为奇数返回false
然后试着将所有数字分成两份,如果其中有一份能达到sum/2 ,返回true,否则为false。

DFS, 暴力过不了所有的点

    public static boolean canPart( int[] nums ) {
        int sum = 0;
        for ( Integer i : nums ) {
            sum += i;
        }
        return dfs( nums, 0, 0, sum );
    }
    private static boolean dfs( int[] nums, int index, int sum, int target ) {
        //base case
        if ( nums.length == index ) {
            if ( sum == target ) {
                return true;
            } else {
                return false;
            }
        }
        //对于任意一个数,可与选或者不选
        return dfs( nums, index + 1, sum + nums[index], target )
                ||
                dfs( nums, index + 1, sum, target );
    }
//    作者:winlsr
//    链接:https://leetcode-cn.com/problems/partition-equal-subset-sum/solution/dfs-dfs-bei-wang-lu-01bei-bao-dong-tai-g-4j1t/
//    来源:力扣(LeetCode)
//    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

暴力+记忆化搜索

 // 备忘录:也可用一个二维数组,一维表示元素和sum,一维表示当前索引index
    private Map<String, Boolean> map = new HashMap<>();
    private boolean dfsAndMap( int[] nums, int index, int sum, int target ) {
        if ( nums.length == index ) {
            if ( sum == target ) {
                return true;
            } else {
                return false;
            }
        }
        //描述一个子问题的两个变量是 sum 和 index,组成 key 字符串
        String key = sum + "&" + index;
        if ( map.containsKey( key ) ) {
            return map.get( key );
        }
        boolean ret = dfs( nums, index + 1, sum + nums[index], target ) ||
                dfs( nums, index + 1, sum, target );
        map.put( key, ret );
        return ret;
    }

动态规划

等价关系是这样的:
从 [0…i] 这个区间里抽出若干个元素能够填满 sum / 2 的背包,
就一定可以把区间为全部、和为 sum 的整数拆分成两个相等的子集【1】【2】

dp[ i ][ j ] 的意思是,选 i 个数字,刚好等于 j
因此dp [ i ] [ j ]有两种状态,true或者false。

    public boolean canPartition(int[] nums) {
        int sum = 0;
        for (int num : nums) {
            sum += num;
        }
        if (sum % 2 == 1) {
            return false;
        }
        int target = sum / 2;
        int n = nums.length;
        boolean[][] dp = new boolean[n + 1][target + 1];
        dp[0][0] = true;

        for(int i=1;i<=n;i++){
            for(int j=1;j<=target;j++){
            // 不选,则当前状态和上一层状态相同
                if(j < nums[i-1]){
                    dp[i][j]=dp[i-1][j];
                }else{
                // 选了当前元素, 上一层布尔值 || 选了当前元素的布尔值 
                    dp[i][j]=dp[i-1][j] || dp[i-1][j-nums[i-1]];
                }
            }
        }
        return dp[n][target];
    }
一维优化
        public boolean canPartition(int[] nums) {
        int sum = 0;
        for (int num : nums) {
            sum += num;
        }
        if (sum % 2 == 1) {
            return false;
        }
        int target = sum / 2;
        int n = nums.length;
        boolean[] dp = new boolean[target + 1];
        dp[0] = true;

        for(int i=1;i<=n;i++){
            for(int j=target;j>=nums[i-1];j--){
                    dp[j]= dp[j-nums[i-1]] || dp[j];
            }
        }
        return dp[target];
    }

1049. 最后一块石头的重量 II

在这里插入图片描述

动态规划
class Solution {
public int lastStoneWeightII(int[] stones) {
        int sum = 0;
        for (int s : stones) {
            sum += s;
        }

        int target = sum / 2;
        //初始化,dp[i][j]为可以放0-i物品,背包容量为j的情况下背包中的最大价值
        int[][] dp = new int[stones.length][target + 1];
        //dp[i][0]默认初始化为0
        //dp[0][j]取决于stones[0]
        for (int j = stones[0]; j <= target; j++) {
            dp[0][j] = stones[0];
        }

        for (int i = 1; i < stones.length; i++) {
            for (int j = 1; j <= target; j++) {//注意是等于
                if (j >= stones[i]) {
                    //不放:dp[i - 1][j] 放:dp[i - 1][j - stones[i]] + stones[i]
                    dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - stones[i]] + stones[i]);
                } else {
                    dp[i][j] = dp[i - 1][j];
                }
            }
        }

       // System.out.println(dp[stones.length - 1][target]);
        return (sum - dp[stones.length - 1][target]) - dp[stones.length - 1][target];
    }
}

494. 目标和

在这里插入图片描述

DFS 搜索
    int res = 0;
    public int findTargetSumWaysByDFS( int[] nums, int target ) {
        int sum = 0;
        for ( Integer i : nums ) {
            sum += i;
        }
        if ( sum < target ) {
            return 0;
        }
        dfs( nums, target, 0, 0 );
        return res;
    }
    public void dfs( int[] nums, int target, int sum, int index ) {
        if ( index == nums.length ) {
            if ( sum == target ) {
                res++;
                return;
            }
        }
        if ( index != nums.length ) {
            dfs( nums, target, sum - nums[index], index + 1 );
            dfs( nums, target, sum + nums[index], index + 1 );
        }
    }

DFS 方法二

class Solution {
    public int findTargetSumWays(int[] nums, int t) {
        return dfs(nums, t, 0, 0);
    }
    int dfs(int[] nums, int t, int u, int cur) {
        if (u == nums.length) {
            return cur == t ? 1 : 0;
        }
        int left = dfs(nums, t, u + 1, cur + nums[u]);
        int right = dfs(nums, t, u + 1, cur - nums[u]);
        return left + right;
    }
}
作者:AC_OIer
链接:https://leetcode-cn.com/problems/target-sum/solution/gong-shui-san-xie-yi-ti-si-jie-dfs-ji-yi-et5b/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
二维动态规划

目标和:给数组里的每个数字添加正负号得到target
数组和sum,目标和targer,
数字可以分为两堆,左边组合 x , 右边组合 y ,

x + y = sum , x - y = targer ,
那么
x + x = sum + targer
x = ( target + sum ) / 2 【3】

target是固定的,sum是固定的,y 就可以求出来
此时问题就是在集合nums中找出和为x的组合。

也就是说问题有解需要满足两个条件

  1. 数字全部加起来大于 target
  2. (sum + target) % 2 != 0 要不然无法找到相应的左右组合使得题意成立。

定义dp[ i ] [ j ]
表示, 选 0-i 个数,恰好使得他们的和为 j 的方法数有 dp[ i ] [ j ] 种。
初试化 dp[ 0 ] [ 0 ] = 1 表示一个数都不选,他们的和恰好为0 的方法数有 1 种

对于第i个数字,我们有两种选择:

  1. 选择将第 i 个数字放入左边的组合,
    此时,数字还剩 i - 1 个,背包的容量需要减去nums [ i ] (此处相当于要凑 j ,还差 j-nums[i] , 而这个信息在 dp [ i - 1 ] [ j - nums [ i ] ] 存放着)

    递推式为 dp [ i ] [ j ] = dp [ i - 1 ] [ j - nums [ i ] ]

  2. 选择放弃第i个数字,直接来到第i - 1个数字,
    那么能满足和为 j 的方式就是上一行的值(dp[ i - 1] [ j ]

    递推式为 dp [ i ] [ j ] = dp [ i - 1 ] [ j ]

  3. 当背包的容量小于第i个数字时,即j< nums[ i ],无法将第i个数字放入背包,只能跳过,递推式同2 【4】

此处相当于,剩下要凑的 数字 j 已经比 其他数字小了,无法
所以 dp [ i ] [ j ] = dp [ i - 1 ] [ j ]

public int findTargetSumWays( int[] nums, int target ) {
        int res = 0;
        int sum = 0;
        for ( int i : nums ) {
            sum += i;
        }
        if ( Math.abs( target ) > sum || (sum + target) % 2 != 0 ) {
            return 0;
        }
        int mid = (sum + target) / 2;
        int N = nums.length;
        int[][] dp = new int[N + 1][mid + 1];
        dp[0][0] = 1;

        for ( int i = 1; i <= N; i++ ) {
            for ( int j = 0; j <= mid; j++ ) {
                if ( j >= nums[i - 1] ) {
                    // 两种选择的结果之和
                    dp[i][j] = dp[i - 1][j] + dp[i - 1][j - nums[i - 1]];
                } else {
                    // 背包的空间不足,只能选择不装物品 i
                    dp[i][j] = dp[i - 1][j];
                }
            }
        }
        return dp[N][mid];
    }
一维优化
    public int findTargetSumWays(int[] nums, int target) {
    
        int res = 0;
         int sum = 0;
        for (int i : nums) {
            sum += i;
        }
        
        if (Math.abs(target) > sum || (sum + target ) % 2 != 0 ) {
            return 0;
        }

        int mid =  (sum + target) / 2;
        int N = nums.length;
        int []dp = new int [mid+1];
        dp[0]=1;

        for (int i = 1; i <= N; i++) {
                for (int j = mid; j>=nums[i-1]; j--) {
                        dp[j] = dp[j] + dp[j-nums[i-1]];
                }
            }
            return dp[mid];
    }

474. 一和零

在这里插入图片描述

暴力超时
int res = 0 ;    
    public int findMaxForm(String[] strs, int m, int n) {
        List<String> path = new ArrayList<>();
        get(strs,0,m,n,0,0,path);
        return res;
    }

    public void get(String[] strs,int index, int m, int n,int numM,int numN,List<String> path  ){

        if(numM <=m && numN<= n){
            res = Math.max(res,path.size());
        }else{
            return;
        }

        for(int i=index;i<strs.length;i++){
            path.add(strs[index]);
            int[] cur = count(strs[i]);
            get(strs,i+1,m,n,cur[0]+numM,cur[1]+numN,path);
            path.remove(path.size()-1);
        }
    }
    public int[] count(String s){
        int [] res = new int[2];
        int i = 0;
        int len = s.length();
        while(i<len){
            if(s.charAt(i) == '0'){
                res[0]++;
            }else{
                res[1]++;
            }
            i++;
        }
        return res;
    }
    
动态规划

参考【5】

public class Solution {

    public int findMaxForm(String[] strs, int m, int n) {
        int len = strs.length;
        int[][][] dp = new int[len + 1][m + 1][n + 1];

        for (int i = 1; i <= len; i++) {
            // 注意:有一位偏移
            int[] count = countZeroAndOne(strs[i - 1]);
            for (int j = 0; j <= m; j++) {
                for (int k = 0; k <= n; k++) {
                    // 先把上一行抄下来
                    dp[i][j][k] = dp[i - 1][j][k];
                    int zeros = count[0];
                    int ones = count[1];
                    if (j >= zeros && k >= ones) {
                        dp[i][j][k] = Math.max(dp[i - 1][j][k], dp[i - 1][j - zeros][k - ones] + 1);
                    }
                }
            }
        }
        return dp[len][m][n];
    }

    private int[] countZeroAndOne(String str) {
        int[] cnt = new int[2];
        for (char c : str.toCharArray()) {
            cnt[c - '0']++;
        }
        return cnt;
    }
}

空间优化

public class Solution {

    public int findMaxForm(String[] strs, int m, int n) {
        int[][] dp = new int[m + 1][n + 1];
        dp[0][0] = 0;
        for (String s : strs) {
            int[] zeroAndOne = calcZeroAndOne(s);
            int zeros = zeroAndOne[0];
            int ones = zeroAndOne[1];
            for (int i = m; i >= zeros; i--) {
                for (int j = n; j >= ones; j--) {
                    dp[i][j] = Math.max(dp[i][j], dp[i - zeros][j - ones] + 1);
                }
            }
        }
        return dp[m][n];
    }

    private int[] calcZeroAndOne(String str) {
        int[] res = new int[2];
        for (char c : str.toCharArray()) {
            res[c - '0']++;
        }
        return res;
    }
}

【1】https://leetcode-cn.com/problems/partition-equal-subset-sum/solution/0-1-bei-bao-wen-ti-xiang-jie-zhen-dui-ben-ti-de-yo/782571
【2】https://leetcode-cn.com/problems/partition-equal-subset-sum/solution/0-1-bei-bao-wen-ti-xiang-jie-zhen-dui-ben-ti-de-yo/
【3】https://leetcode-cn.com/problems/partition-equal-subset-sum/solution/yi-pian-wen-zhang-chi-tou-bei-bao-wen-ti-a7dd/
【4】https://leetcode-cn.com/problems/target-sum/solution/hen-xiang-xi-de-zhuan-hua-wei-0-1bei-bao-irvy/
【5】https://leetcode-cn.com/problems/ones-and-zeroes/solution/dong-tai-gui-hua-zhuan-huan-wei-0-1-bei-bao-wen-ti/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值