代码随想录算法训练营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来推出当前状态的递推方式

组合数与排列数

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

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


相关推荐
rannn_1111 小时前
【力扣hot100】链表专题|160、206、234、141、142
java·算法·leetcode·链表·面试·开发
数模竞赛Paid answer1 小时前
2026年华东杯数学建模B题医药物流安排问题解题全过程文档及程序
算法·数学建模·数据分析·华东杯
不会代码的小猴2 小时前
21. 泛型编程上
开发语言·c++·笔记·算法
营养充电站2 小时前
VS Code Git 工作树:解锁多分支并行开发的高效体验
leetcode·决策树·逻辑回归·散列表·模拟退火算法
青瓦梦滋2 小时前
传输层UDP/TCP协议
linux·网络·c++·网络协议·tcp/ip·udp
AI备案指南-满满2 小时前
大模型与算法备案全流程详解:从零到通过的完整指南
人工智能·算法·备案·大模型备案·算法备案
江畔柳前堤2 小时前
LLM 训练核心机制深度解析:Warmup、Cosine Decay 与 Perplexity 的完整知识体系
网络·人工智能·深度学习·算法·机器学习·语音识别
一只旭宝2 小时前
细讲C加加【9】C++ std::function与std::bind详解|仿函数、绑定器、类成员绑定、占位符、成员偏移指针
开发语言·c++·算法
良木林3 小时前
子串 - LeetCode hot 100
算法·leetcode·职场和发展