01背包类算法题

关于背包问题的概念可以参考此处

leetcode 416. 分割等和子集

给定一个只包含正整数的非空数组。是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。

示例 1: 输入: [1, 5, 11, 5],输出: true

解释: 数组可以分割成 [1, 5, 5] 和 [11].

示例 2:输入: [1, 2, 3, 5],输出: false

其实可以看成是一个01背包问题。能不能装满sum/2,能的话就返回true,否则返回false。

public boolean canPartition(int[] nums) {
        if (nums == null || nums.length == 0) {
            return false;
        }

        int sum = 0;
        for (int num : nums) {
            sum += num;
        }
        if (sum % 2 != 0) {
            return false;
        }
        sum = sum / 2;
        boolean[] res = new boolean[sum + 1];
        res[0] = true;
        for (int num : nums) {
            for (int i = sum; i >= num; i--) {
                res[i] = res[i] || res[i - num];
            }
        }

        return res[sum];
    }

leetcode 322. 零钱兑换

给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。

示例 1:

输入: coins = [1, 2, 5], amount = 11 输出: 3 解释: 11 = 5 + 5 + 1

示例 2: 输入: coins = [2], amount = 3  输出: -1

说明:你可以认为每种硬币的数量是无限的。

可以看成是一个完全背包问题。用一个完全背包装满指定的金额

public int coinChange(int[] coins, int amount) {
        if (coins == null || coins.length == 0 || amount == 0) {
            return 0;
        }
        int[] dp = new int[amount + 1];
        for (int coin : coins) {
            for (int i = coin; i <= amount; i++) {
                //如果需要组成的金额正好和某个硬币的面额相等
                if (coin == i) {
                    dp[i] = 1;
                } else {
                    //只有能凑成dp[i - coin]才能凑成dp[i]
                    if (dp[i - coin] != 0) {
                        if (dp[i] == 0) {
                            //暂时能凑成dp[i-coin],但是凑不成dp[i],那么直接将dp[i-coin]+1
                            dp[i] = dp[i - coin] + 1;
                        } else {
                            //既能凑成dp[i-coin],又能凑不成dp[i],那么取dp[i-coin]+1和dp[i]的较小值
                            dp[i] = Math.min(dp[i], dp[i - coin] + 1);
                        }

                    }

                }
            }
        }
        return dp[amount] == 0 ? -1 : dp[amount];

    }

leetcode 518. 零钱兑换 II

给定不同面额的硬币和一个总金额。写出函数来计算可以凑成总金额的硬币组合数。假设每一种面额的硬币有无限个。

示例 1: 输入: amount = 5, coins = [1, 2, 5] 输出: 4
解释: 有四种方式可以凑成总金额:
5=5
5=2+2+1
5=2+1+1+1
5=1+1+1+1+1

示例 2: 输入: amount = 3, coins = [2] 输出: 0
解释: 只用面额2的硬币不能凑成总金额3。

因为每种面额是无限的,因此也是一个完全背包问题。

d 为动态规划记录数组:

d[i] 代表,最多 i 元钱时可兑换的分配方案数量;

d[0] 时为初始条件,表示0元可以用0元兑换1次,所以 d[0] = 1。

d[i - c] 中,c 代表当前面值,d[i - c] 表示 i - c 元钱所有分配方案之和,d[i] 分配方案应该等于所有 d[i - c] 分配方案之和,这样就可以快速的计算出 d[amount] 的和了。

public int change(int amount, int[] coins) {
        if (coins == null) {
            return 0;
        }
        int[] dp = new int[amount + 1];
        dp[0] = 1;
        for (int coin : coins) {
            for (int i = coin; i <= amount; i++) {
                dp[i] += dp[i - coin];
            }
        }
        return dp[amount];

    }

leetcode 139. 单词拆分

给定一个非空字符串 s 和一个包含非空单词列表的字典 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。

说明:拆分时可以重复使用字典中的单词。
    你可以假设字典中没有重复的单词。

示例 1:输入: s = "leetcode", wordDict = ["leet", "code"]
输出: true
解释: 返回 true 因为 "leetcode" 可以被拆分成 "leet code"。

示例 2:输入: s = "applepenapple", wordDict = ["apple", "pen"]
输出: true
解释: 返回 true 因为 "applepenapple" 可以被拆分成 "apple pen apple"。
     注意你可以重复使用字典中的单词。

示例 3:输入: s = "catsandog", wordDict = ["cats", "dog", "sand", "and", "cat"]
输出: false

因为可以重复使用,所以这是一个经典的完全背包问题,dp[i]表示从0到i能否拆分。

状态转移方程为:dp[i] = s[j-->i] in wordDict and dp[j]。

注意 :需要定义dp[0]为 true,因为如果字符串本身就在 wordDict 中,就不必看 dp 了,可以直接判断为 true,因此 dp[0] = true;

public boolean wordBreak(String s, List<String> wordDict) {
        if (s == null) {
            return true;
        }
        if (wordDict == null || wordDict.size() == 0) {
            return false;
        }

        Set<String> set = new HashSet<>(wordDict);
        boolean[] dp = new boolean[s.length() + 1];

        dp[0] = true;
        for (int i = 1; i <= s.length(); i++) {
            for (int j = 0; j < i; j++) {
                if (dp[j] && set.contains(s.substring(j, i))) {
                    dp[i] = true;
                    break;
                }
            }
        }

        return dp[s.length()];

    }

leetcode 377. 组合总和 Ⅳ

给定一个由正整数组成且不存在重复数字的数组,找出和为给定目标正整数的组合的个数。

示例:

nums = [1, 2, 3]target = 4  所有可能的组合为:
(1, 1, 1, 1)
(1, 1, 2)
(1, 2, 1)
(1, 3)
(2, 1, 1)
(2, 2)
(3, 1)

请注意,顺序不同的序列被视作不同的组合。

因此输出为 7。

此题的状态转移方程为dp[i] = dp[i-nums[0]]+dp[i-nums[1]]+...dp[i-nums[len-1]],条件为i>=nums[j];
dp[0] = 1,dp[0]表示组成0,一个数都不选就可以了,所以dp[0]=1
举个例子。假设nums={1,2,3}; target = 4

dp[4] = dp[4-1]+dp[4-2]+dp[4-3] = dp[3]+dp[2]+dp[1]

dp[1] = dp[0] = 1;
dp[2] = dp[1]+dp[0] = 2;
dp[3] = dp[2]+dp[1]+dp[0] = 4;
dp[4] = dp[4-1]+dp[4-2]+dp[4-3] = dp[3]+dp[2]+dp[1] = 7
代码如下

public int combinationSum4(int[] nums, int target) {
        if (nums == null) {
            return 0;
        }
        int[] dp = new int[target + 1];
        //dp[0]表示组成0,一个数都不选就可以了,所以dp[0]=1
        dp[0] = 1;
        for (int i = 1; i <= target; i++) {
            for (int num : nums) {
                if (i >= num) {
                    dp[i] += dp[i - num];
                }
            }

        }
        return dp[target];
    }

 

上一篇:vue v-for遍历数组和遍历对象的区别


下一篇:一维动态规划中的一些思考