套用解题模板---对背包问题的三种类分类

对背包问题的三种类分类

CS-Note关于动态规划的文章

1.排列组合问题

image-20200806101745087

518. 零钱兑换 II (完全背包+顺序无关)

物品的数量是无限的

明确问题的性质

  • 背包问题
  • 组合问题(顺序是无关的)
  • 完全背包问题(可以重复选择num)

组合问题: dp[i] += dp[i-num]

完全背包 且 顺序无关: nums在外 target在内

image-20200805184335769

class Solution {
   public int change(int amount, int[] coins) {
        int n = coins.length;
        int[] dp = new int[amount + 1];
        dp[0] = 1;

        for (int num : coins) {
            for (int i = 1; i <= amount; i++) {
                if (i >= num) {
                    dp[i] += dp[i - num];
                }
            }
        }
        return dp[amount];

    }
}

494. 目标和(0-1背包+顺序无关)

image-20200807223800060

明确问题的性质

  • 背包问题
  • 组合问题(顺序没有影响)
  • 0-1背包问题(每个num只能使用一次)

组合问题: dp[i] += dp[i-num]

0-1背包问题,且,是组合问题(顺序不影响) nums在外, target在内,且target倒序循环

题解

把所有符号为正的数总和设为一个背包的容量,容量为x;

把所有符号为负的数总和设为一个背包的容量,容量为y。

在给定的数组中,有多少种选择方法让背包装满。
令sum为数组的总和,则x+y = sum。
而两个背包的差为S,则x-y=S。(S已知)

从而求得x=(S+sum)/2。

基于上述分析,题目转换为背包问题:给定一个数组和一个容量为x的背包,求有多少种方式让背包装满。

class Solution {
    public int findTargetSumWays(int[] nums, int S) {
        int sum = 0;
        for (int num : nums) {
            sum += num;
        }

        if (S>sum || (S+sum)%2==1) return 0;


        int target = (S + sum) / 2;
        int[] dp = new int[target + 1];
        dp[0] = 1;

        for (int num : nums) {
            for (int i = target; i >= 0; i--) {
                if (i >= num) {
                    dp[i] += dp[i - num];
                }
            }
        }

        return dp[target];
    }
}

377. 组合总和 Ⅳ (完全背包+顺序有关)

先给这道题定性

  • 背包问题
  • 排列问题(先后顺序是有影响的)
  • 完全背包问题(可以重复选用同一个数字)

排列问题: dp[i] += dp[i-num]

完全背包问题:且是 排列问题(顺序有关)

  • target在外, nums在内

    image-20200807230549786

image-20200803132156074

image-20200807222434147

看懂的题解

public int combinationSum4(int[] nums, int target) {

    // 状态 选择  dp  base case
    int[] dp = new int[target + 1];
    dp[0] = 1;

    for (int i = 1; i <= target; i++) {
        for (int num : nums) {
            if (num <= i) {
                dp[i] = dp[i] + dp[i - num];// 上一个num选择下的组合可能数+这个num选择下可能的组合个数
            }
        }
    }

    return dp[target];

}

2.True、False问题公式

image-20200806101907759

416. 分割等和子集(0-1背包 + 顺序无关)

image-20200805161053766

可以使用动态规划解决

也可以使用 回溯+剪枝

明确问题性质

  • 可以是背包问题
  • 组合问题(顺序不影响)
  • 0-1背包问题(num不可重复使用)

组合问题: dp[i] = dp[i] || dp[i-num]

0-1背包 且 组合: nums 在外循环, target在内循环 且 逆序

public boolean canPartition(int[] nums) {

        int sum = 0;
        for (int num : nums) sum += num;

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

        boolean[] dp = new boolean[target + 1];
        dp[0] = true;
        
        for (int num : nums) {
            for (int i = target; i >= 0; i--) {
                if (i >= num) {
                    dp[i] = dp[i] || dp[i - num];
                }
            }
        }

        return dp[target];
}

139. 单词拆分 (完全背包+顺序有关)

image-20200807235551161

给问题定性

  • 是背包问题(target是字符串s, nums是字典列表)
  • true false 问题(且 顺序是有关的,同一个单词前后顺序是有影响的)
  • 完全背包(num可以重复选取)

排列问题: dp[i] = dp[i] || dp[i-num]

完全背包 且 顺序有关: target在外循环 nums在内循环

public class Number39_wordBreak {

    // true false 问题
    // 完全背包问题
    // 顺序有关

    public boolean wordBreak(String s, List<String> wordDict) {
        int target = s.length();
        boolean[] dp = new boolean[target + 1];

        dp[0] = true;

        for (int i = 1; i <= target; i++) {
            for (String word : wordDict) {
                int len = word.length();
                if (i >= len && word.equals(s.substring(i - len, i))) {
                    dp[i] = dp[i] || dp[i - len];
                }
            }
        }

        return dp[target];
    }
}

3.最大最小问题公式

image-20200806101913679

322. 零钱兑换 (完全背包+顺序无关)

image-20200803220306220

明确问题性质

  • 背包问题 (target nums)
  • 最大最小值问题
  • 完全背包问题(顺序无关)

最大最小值问题: dp[i] = min(dp[i], dp[i-num]+1) 或者 dp[i] = max(dp[i], dp[i-num]+1)

完全背包问题: 且 顺序无关 nums 在外循环、target在内循环

public int coinChange(int[] coins, int amount) {

        int[] dp = new int[amount + 1];
        // basecase
        Arrays.fill(dp, amount + 1);
        dp[0] = 0;

        for (int coin : coins) {
            for (int i = 1; i <=amount ; i++) {
                if (i >= coin) {
                    dp[i] = Math.min(dp[i], dp[i - coin] + 1);
                }
            }
        }
        
        return dp[amount] == (amount + 1) ? -1 : dp[amount];

}

474. 一和零 (01背包+顺序无关)

image-20200815153901768

明确问题性质

  • 最大最小值问题
  • 多维费用的 0-1 背包问题, 有两个背包, 0的数量和1的数量
  • 这里array中的字符,就相当于 nums[]中的数字
    背包容量就是拥有的数字的个数
public int findMaxForm(String[] strs, int m, int n) {

        // 这是一个多为费用的0-1背包问题,
        // 有两个背包,大小是0的数量和1的数量

        // sc
        if (strs == null || strs.length==0) return 0;

        int[][] dp = new int[m + 1][n + 1];

        // 遍历nums
        for (String s : strs) {
            // 计算这个str有多少个0 多少个1
            int ones = 0, zeros = 0;
            for (char c : s.toCharArray()) {
                if (c == '0') {
                    zeros++;
                } else {
                    ones++;
                }
            }
//            System.out.println("ones: " + ones);
//            System.out.println("zeros: " + zeros);

            // 计算不同背包容量下的可能得到的结果
            for (int i = m; i >= 0; i--) {
                for (int j = n; j >= 0; j--) {
                    if (i >= zeros && j >= ones) {
                        dp[i][j] = Math.max(dp[i][j], dp[i - zeros][j - ones] + 1);
                    }
                }
            }
        }
        return dp[m][n];
 }
posted @ 2020-08-15 23:53  唐啊唐囧囧  阅读(506)  评论(0编辑  收藏  举报