代码随想录算法训练营Day-38动态规划06 | 322. 零钱兑换、279.完全平方数、139.单词拆分、多重背包、总结

目录

[322. 零钱兑换](#322. 零钱兑换)

动规五部曲

279.完全平方数

动规五部曲

为什么相比上一题不需要if判断

139.单词拆分

动规五部曲

多重背包

问题解释

代码的改动

总结

三种背包问题

两种递推公式

组合数与排列数


322. 零钱兑换

动规五部曲

1.dpj的含义是,凑齐j所用最少硬币数

2.递推公式:

如果取当前硬币i ,就需要dpj-coins\[i]个硬币数再+1;
如果不取当前硬币i (前i-1个硬币就已经凑齐j) dpj个硬币,二者取最小值

最终递推公式为dpj=min(dpj-coins\[i]+1, dpj)

3.初始化方式:凑齐0元需要0个硬币,所以dp0=0; 其余初始化为最大值,否则会无法被递推公式覆盖。

4.遍历顺序 :可以重复取硬币-正序遍历背包 ;求的是硬币最小数 ,所以先物品(硬币)后背包或者先背包后物品都无所谓

cpp 复制代码
class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        //dp数组:dp[j]的含义是,凑齐j所用最少硬币数
        vector<int> dp(amount+1, INT_MAX);
        //初始化:凑齐0元需要0个硬币,并且其余可初始化为最大值,否则会无法被递推公式覆盖。
        dp[0] =0;
        //递推公式:如果取当前硬币i,就需要dp[j-coins[i]]个硬币数再+1;
        //如果不取当前硬币i(前i-1个硬币就已经凑齐j) dp[j]个硬币,二者取最小值
        //故最终递推公式为dp[j]=min(dp[j-coins[i]]+1, dp[j])

        //遍历顺序:可以重复取硬币-正序遍历背包;硬币数和顺序无关,所以是组合数,所以先硬币后背包;
        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-coins[i]]+1, dp[j]);
            }
        }
        if(dp[amount]==INT_MAX) return -1;

        return dp[amount];

    }
};

279.完全平方数

动规五部曲

1.dpj的含义是,dpj的含义是,凑齐n所用最少完全平方数

2.递推公式:

如果取当前i的平方, 就需要dpj-i\*i个完全平方数再+1;
如果不取当前i的平方 (前i-1个完全平方数就已经凑齐j),则就是不更新的dpj,二者取最小值

故最终递推公式为dpj=min(dpj-coins\[i]+1, dpj)

3.初始化方式:凑齐0需要0个完全平方数,并且其余可初始化为最大值,否则会无法被递推公式覆盖。

4.遍历顺序 :可以重复取硬币-正序遍历背包;可以重复取完全平方数-正序遍历背包;求最小数,所以先物品后背包或者先背包后物品都可以;

为什么相比上一题不需要if判断

**先背包后物品:**遍历容量1时,只能塞下1,所以dp1一定是1,有了这个基础,后面再遍历的时候不需要考虑前面还没有有效值覆盖的情况从而出现INT_MAX;

**先物品后背包:**遍历平方数1的时候,会把所有容量用1填满,同样每个值都已经变成了有效值,不是INT_MAX了,所以可以。

注:本题一定有结果,因为1可以填任意值。

cpp 复制代码
class Solution {
public:
    int numSquares(int n) {
        //dp数组:dp[j]的含义是,凑齐n所用最少完全平方数
        vector<int> dp(n+1, INT_MAX);
        //初始化:凑齐0需要0个完全平方数,并且其余可初始化为最大值,否则会无法被递推公式覆盖。
        dp[0] =0;
        //递推公式:如果取当前i的平方,就需要dp[j-i*i]个完全平方数再+1;
        //如果不取当前i的平方(前i-1个完全平方数就已经凑齐j),则就是不更新的dp[j],二者取最小值
        //故最终递推公式为dp[j]=min(dp[j-coins[i]]+1, dp[j])

        //遍历顺序:可以重复取完全平方数-正序遍历背包;求最小数,所以先物品后背包或者先背包后物品都可以;
        for(int i=1;i*i<=n;i++){
            for(int j=i*i;j<=n;j++){
                dp[j] = min(dp[j - i * i] + 1, dp[j]);
            }
        }
        return dp[n];
    }
};

139.单词拆分

动规五部曲

1.dpj的含义是,dpj的含义是,长度为j的字符串,是否能被字典中字符串填满

2.递推公式:

如果sj:i这个字符子串在字典内,并且dpj为true,则dpi为true

3.初始化方式:dp0必须为true,否则后面全为false了;其余初始值为false,否则最后直接输出true了;

4.遍历顺序

可以重复取字符串-正序遍历背包

物品的顺序会影响dp值,所以是排列数,需要先背包后物品

**注:**物品是字典中的字符串,但需要先匹配上,也就是用j遍历到当前容量(字符串索引),截取i-j长度的子串,如果和字典中的某元素能对上,则为有效物品,可以进行下一步判断(判断dpj是否为true)

cpp 复制代码
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 i=1;i<=s.size();i++){
            for(int j=0;j<i;j++){
                string str = s.substr(j,i-j);
                if(wordSet.find(str)!=wordSet.end() && dp[j]) dp[i]=true;
            }
        }

        return dp[s.size()];
    }
};

多重背包

问题解释

有N种物品和一个容量为V 的背包。第i种物品最多有Mi件可用,每件耗费的空间是Ci ,价值是Wi 。求解将哪些物品装入背包可使这些物品的耗费的空间 总和不超过背包容量,且价值总和最大。

只要把每种物品的Mi件展开,就可以转化为0-1背包问题;

代码的改动

在0-1背包的先物品后背包(倒序)的基础上,再加一层循环,意思是,对于第i种物品,求放1次、放2次、...、放Mi次到底放多少次价值最大

cpp 复制代码
for(int i = 0; i < n; i++) { // 遍历物品
    for(int j = bagWeight; j >= weight[i]; j--) { // 遍历背包容量
        // 以上为01背包,然后加一个遍历个数
        //遍历放入个数时,需满足背包容量大于等于k个物品i的重量
        for (int k = 1; k <= nums[i] && (j - k * weight[i]) >= 0; k++) { // 遍历个数
            dp[j] = max(dp[j], dp[j - k * weight[i]] + k * value[i]);
        }
    }
}

总结

三种背包问题

**0-1背包:**先物品后背包+背包倒序

**完全背包:**先物品后背包或先背包后物品或两种都可以+背包正序

**多重背包(0-1背包变种):**同0-1背包

两种递推公式

最值问题(尽量装满背包情况下):

cpp 复制代码
dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
//dp[j] = min(dp[j], dp[j - weight[i]] + value[i]);

最多方案数问题:

cpp 复制代码
dp[j] += dp[j - weight[i]];

其实不止两种,非典型的还有单词拆分 这种根据字符子串是否匹配物品加上新子串前是否为true来推出当前状态的递推方式

组合数与排列数

**组合数:**先物品后背包-先物品则路径固定;

**排列数:**先背包后物品-后物品可遍历选择最后一个位置放哪个物品;


相关推荐
AI小老六8 分钟前
SkillOpt 架构拆解:把 Skill 文本当参数,用执行轨迹训练 Agent
后端·算法·ai编程
胡萝卜术1 小时前
从“分数打架”到“排名投票”:为什么你的ChatBI必须用RRF?
算法·设计模式·面试
Asize1 小时前
初识DFS 与 BFS:递归、队列与图遍历
算法
罗西的思考15 小时前
机器人 / 强化学习】HIL-SERL:人类在环驱动的具身智能进化框架
人工智能·算法·机器学习
CSharp精选营17 小时前
关系型 vs 非关系型:从原理到选型,一文搞定数据库核心分类
数据结构·nosql·关系型数据库·非关系型数据库·技术选型
美团技术团队18 小时前
LongCat 开源 VitaBench 2.0:长期动态智能体基准新标杆
人工智能·算法
用户805533698031 天前
不止三件套:QObject 属性系统全关键字与运行时反射!
c++·qt
To_OC1 天前
LC 207 课程表:刚学图论那会儿,我连这是拓扑排序都没看出来
javascript·算法·leetcode
To_OC1 天前
LC 208 实现 Trie 前缀树:曾被名字劝退,写完发现是送分题
javascript·算法·leetcode
BadBadBad__AK2 天前
线段树维护区间 k 次方和
c++·数学·算法·stl