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

目录

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

动规五部曲

279.完全平方数

动规五部曲

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

139.单词拆分

动规五部曲

多重背包

问题解释

代码的改动

总结

三种背包问题

两种递推公式

组合数与排列数


322. 零钱兑换

动规五部曲

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

2.递推公式:

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

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

3.初始化方式:凑齐0元需要0个硬币,所以dp[0]=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.dp[j]的含义是,dp[j]的含义是,凑齐n所用最少完全平方数

2.递推公式:

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

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

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

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

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

**先背包后物品:**遍历容量1时,只能塞下1,所以dp[1]一定是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.dp[j]的含义是,dp[j]的含义是,长度为j的字符串,是否能被字典中字符串填满

2.递推公式:

如果s[j:i]这个字符子串在字典内,并且dp[j]为true,则dp[i]为true

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

4.遍历顺序

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

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

**注:**物品是字典中的字符串,但需要先匹配上,也就是用j遍历到当前容量(字符串索引),截取i-j长度的子串,如果和字典中的某元素能对上,则为有效物品,可以进行下一步判断(判断dp[j]是否为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种物品最多有M[i]件可用,每件耗费的空间是C[i] ,价值是W[i] 。求解将哪些物品装入背包可使这些物品的耗费的空间 总和不超过背包容量,且价值总和最大。

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

代码的改动

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

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来推出当前状态的递推方式

组合数与排列数

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

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


相关推荐
jimy11 小时前
C语言历史版本和gnu扩展版本
c语言·算法·gnu
shehuiyuelaiyuehao1 小时前
关于算法14,15解决一些问题
算法
探序基因1 小时前
单细胞转录组Seurat去批次-FastMNN算法及大细胞量评测
linux·算法
阿Y加油吧1 小时前
二刷 LeetCode:300. 最长递增子序列 & 152. 乘积最大子数组 复盘笔记
笔记·算法·leetcode
我是大聪明.1 小时前
大模型Tokenizer原理:BPE、WordPiece与子词编码的核心机制深度解析
人工智能·线性代数·算法·机器学习·矩阵
自我意识的多元宇宙1 小时前
数据结构----希尔排序
数据结构·算法·排序算法
y = xⁿ1 小时前
Redis八股学习日记:数据结构;跳表的底层;Reids的事务机制
数据结构·redis·学习
6Hzlia1 小时前
Hot 100 刷题计划】 LeetCode 146. LRU 缓存 | C++ 哈希表+双向链表
c++·leetcode·缓存
惊鸿一博1 小时前
深度学习特征匹配算法 LoFTR、DKM、RoMa 介绍
人工智能·深度学习·算法