代码随想录算法训练营Day33| LeetCode 322 零钱兑换、279 完全平方数、139 单词拆分、多重背包问题

力扣 322 零钱兑换

本问题是一个组合问题,目的是获得硬币更少的找零方案。这里初始化需要设置为INT_MAX ,循环中用min来更新数组。同时开始前需将dp[0]标记为0,即最少使用0个硬币即可得到数额0。整体代码如下:

class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        vector<int> dp(amount + 1, INT_MAX);
        dp[0] = 0;
        for(int i = 0; i < coins.size(); i++){
            for(int j = coins[i]; j <= amount; j++){
                if (dp[j - coins[i]] != INT_MAX){
                    dp[j] = min(dp[j], dp[j - coins[i]] + 1);
                }
            }
        }
        return dp[amount] == INT_MAX ? -1 : dp[amount];
    }
};

还有一点,INT_MAX + 1 会溢出为负数,导致 min(dp[j], dp[j - coins[i]] + 1) 出错。因此保险起见,循环中需要加上 if 防止溢出。

力扣 279 完全平方数 AC

第一遍ac代码如下:

class Solution {
public:
    int numSquares(int n) {
        int m = sqrt(n);
        vector<int> dp(n + 1);
        for(int j = 0; j <= n; j++){
            dp[j] = j;
        }
        for(int i = 2; i <= m; i++){
            for(int j = i * i; j <= n; j++){
                dp[j] = min(dp[j], dp[j - i * i] + 1);
            }
        }
        return dp[n];
    }
};

后发现因为最小值是1,所以无需额外初始化,可以放到整体的遍历过程里:

class Solution {
public:
    int numSquares(int n) {
        int m = sqrt(n);
        vector<int> dp(n + 1, INT_MAX);
        dp[0] = 0;
        for(int i = 1; i <= m; i++){
            for(int j = i * i; j <= n; j++){
                dp[j] = min(dp[j], dp[j - i * i] + 1);
            }
        }
        return dp[n];
    }
};

这里在更新第一个物品 1 2 1^2 12 的时候就已经把所有dp[j]初始化了,所以无需进一步担心循环中可能有INT_MAX + 1溢出的问题,没加额外判断。

力扣 139 单词拆分

定义dp数组dp[j] 表示字符串长度为j时,如果可以拆分为字典里的一个或者多个词汇,那么记为true。本题本质上是排列,因此外层遍历背包,内层遍历物品。但对于本题,内层循环的写法稍有不同,它尝试把 [ 0 , j − 1 ] [0,j-1] [0j1] 拆成两部分进行判断:

  • 前半部分 [ 0 , i − 1 ] [0,i-1] [0,i1] 是否可拆 ,这里使用 dp[i] 判断。
  • 后半部分 [ i , j − 1 ] [i,j-1] [i,j1] 是否在字典中,即i是否为最后一个单词的起点。

相当于dp[i]是一个已知状态,它表示前i个字符是否可以拆分,而dp[j]是一个待判断的新状态,我们正在尝试dp[i]用来更新它。代码如下:

class Solution {
public:
    bool wordBreak(string s, vector<string>& wordDict) {
        unordered_set<string> wordSet(wordDict.begin(), wordDict.end());
        vector<bool> dp(s.size() + 1, false);
        dp[0] = true;
        for(int j = 1; j <= s.size(); j++){
            for(int i = 0; i < j; i++){
                string word = s.substr(i, j - i);
                if(wordSet.find(word) != wordSet.end() && dp[i]){
                    dp[j] = true;
                }
            }
        }
        return dp[s.size()];
    }
};

多重背包问题

给定一个容量为 W 的背包,和 N 种物品。每种物品有:

  • 一个重量 weight[i]
  • 一个价值 value[i]
  • 最多可用的数量 count[i]

要求在不超过背包容量 W 的前提下,选择若干物品放入背包,使得物品的总价值最大。

但事实上这里把count[i]摊开又是一个0-1背包问题了:

int multipleKnapsack(int bagSize, const vector<int>& weight, const vector<int>& value, const vector<int>& count) {
    vector<int> newWeight;
    vector<int> newValue;

    // 把每个带数量限制的物品拆分成多个 0-1 背包物品
    for (int i = 0; i < weight.size(); i++) {
        for (int k = 0; k < count[i]; k++) {
            newWeight.push_back(weight[i]);
            newValue.push_back(value[i]);
        }
    }

    // 经典 0-1 背包(滚动数组)
    vector<int> dp(bagSize + 1, 0);
    for (int i = 0; i < newWeight.size(); i++) {
        for (int j = bagSize; j >= newWeight[i]; j--) {
            dp[j] = max(dp[j], dp[j - newWeight[i]] + newValue[i]);
        }
    }

    r

背包问题总结

到这里背包问题基本结束,总结一下遇到题型(cr. 代码随想录):

问题类型状态转移公式LeetCode 例题说明
✅ 是否能装满背包(或是最多能装多少)dp[j] = max(dp[j], dp[j - nums[i]] + nums[i])416. 分割等和子集 1049. 最后一块石头的重量 II转换成是否存在子集使和为 target
✅ 装满背包的方案数(组合or排列)dp[j] += dp[j - nums[i]]494. 目标和 518. 零钱兑换 II 377. 组合总和 IV 70. 爬楼梯(完全背包)问方案个数,使用 += 叠加方案数,常用于排列/组合问题
✅ 装满背包的最大价值(最优解)dp[j] = max(dp[j], dp[j - weight[i]] + value[i])474. 一和零标准的 0-1 / 完全 / 多重背包结构,求最大价值
✅ 装满背包所需的最小物品数(最优解)dp[j] = min(dp[j], dp[j - coins[i]] + 1)322. 零钱兑换 279. 完全平方数用最少物品凑出目标
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值