DAY43 ||322.零钱兑换 |279.完全平方数 |139.单词拆分

322.零钱兑换

题目:322. 零钱兑换 - 力扣(LeetCode)

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

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

示例 1:

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

示例 2:

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

思路

dp[j]表示凑够金额所需的最小硬币个数

dp[0]=0表示凑0元所需硬币数是0.其他则初始化为最大值,因为接下来是要找最小值

递推公式则分选择 当前硬币和不选择硬币两种情况。

dp[j]=min(dp[j],dp[j-coins[i]).

遍历顺序先物品在背包或背包物品都不影响,和排列组合无关。内层循环为保证完全背包里的物品可以无限次使用,则要递增遍历。

代码

class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        vector<int>dp(amount+1,INT_MAX);
        dp[0]=0;//凑成金额 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 - coins[i]] == INT_MAX,表示无法用给定硬币凑出金额 j - coins[i],即目前无法到达该状态,所以不应更新 dp[j]。只有当 dp[j - coins[i]] != INT_MAX 时,才有可能通过再加上一个面值为 coins[i] 的硬币凑成金额 j。
                dp[j]=min(dp[j],dp[j-coins[i]]+1);
            }
            
        }
        if(dp[amount]==INT_MAX)return -1;//如果没找到能凑成的
        return dp[amount];
        
    }
};

举例dp(部分)

使用硬币 1

i = 0(硬币面值为 1)时,内层循环更新 dp 数组:

  • 对于 j = 1: dp[1] = min(dp[1], dp[1-1] + 1) = min(INT_MAX, 0 + 1) = 1
  • 对于 j = 2: dp[2] = min(dp[2], dp[2-1] + 1) = min(INT_MAX, 1 + 1) = 2
  • 对于 j = 3: dp[3] = min(dp[3], dp[3-1] + 1) = min(INT_MAX, 2 + 1) = 3
  • 对于 j = 4: dp[4] = min(dp[4], dp[4-1] + 1) = min(INT_MAX, 3 + 1) = 4
  • 对于 j = 5: dp[5] = min(dp[5], dp[5-1] + 1) = min(INT_MAX, 4 + 1) = 5
  • 对于 j = 6: dp[6] = min(dp[6], dp[6-1] + 1) = min(INT_MAX, 5 + 1) = 6
  • 对于 j = 7: dp[7] = min(dp[7], dp[7-1] + 1) = min(INT_MAX, 6 + 1) = 7
  • 对于 j = 8: dp[8] = min(dp[8], dp[8-1] + 1) = min(INT_MAX, 7 + 1) = 8
  • 对于 j = 9: dp[9] = min(dp[9], dp[9-1] + 1) = min(INT_MAX, 8 + 1) = 9
  • 对于 j = 10: dp[10] = min(dp[10], dp[10-1] + 1) = min(INT_MAX, 9 + 1) = 10
  • 对于 j = 11: dp[11] = min(dp[11], dp[11-1] + 1) = min(INT_MAX, 10 + 1) = 11

更新后的 dp 数组是:

dp = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]

279.完全平方数

题目: 279. 完全平方数 - 力扣(LeetCode)

给你一个整数 n ,返回 和为 n 的完全平方数的最少数量

完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,14916 都是完全平方数,而 311 不是。

示例 1:

输入:n = 12
输出:3 
解释:12 = 4 + 4 + 4

示例 2:

输入:n = 13
输出:2
解释:13 = 4 + 9

思路,和上题差不多。dp表示达到背包容量j所需要的完全平方数个数。物品就是i*i。dp【0】初始化为0.dp[j] 可以由dp[j - i * i]推出, dp[j - i * i] + 1 便可以凑成dp[j]。

此时我们要选择最小的dp[j],所以递推公式:**dp[j] = min(dp[j - i * i] + 1, dp[j]);**遍历顺序是背包物品或者物品背包都可以。

举例

版本一

背包物品

class Solution {
public:
    int numSquares(int n) {
        vector<int>dp(n+1,INT_MAX);
        dp[0]=0;

        for(int i=0;i<=n;i++)//背包
        {
            for(int j=1;j*j<=i;j++)//物品
            {
                dp[i]=min(dp[i-j*j]+1,dp[i]);//dp是由前面的数推导过来的
            }
        }
        
     return dp[n];
    }
};

版本二

物品背包

class Solution {
public:
    int numSquares(int n) {
        vector<int>dp(n+1,INT_MAX);
        dp[0]=0;

        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]);//dp[j]表示为了达到容量为j的背包,所需要的完全平方数的最小个数
            }
        }
        
     return dp[n];
    }
};

是的,很简单上面这两题。模板都差不多。

139.单词拆分

思路

单词是物品,字符串是背包,字符串能不能有一个或多个给出的单词组成就是问物品能不能装满背包,而且物品可以重复使用,这就一个完全背包问题。

dp[i] : 字符串长度为i的话,dp[i]为true,表示可以拆分为一个或多个在字典中出现的单词

初始值,dp[0]=true,因为长度0的字符串是可以被0个单词组成的,且如果是false,往下推导就全是false了。其他值则初始化为false。

递推公式:

如果确定dp[j] 是true ,且**[j, i]** 这个区间的子串出现在字典里,那么dp[i]一定是true。(j < i )。

所以递推公式是 if**([j, i] 这个区间的子串出现** 在字典(物品)里 &&dp[j]是true) 那么 dp[i] = true。(进一步解释,这里感觉意思是,字符串前段的单词出现在字典里,后段直到当前背包容量末尾的单词也都在字典里,那么这个字符串一定都出现在字典里,就是背包能被物品装满。)

遍历顺序:如果求排列数就是外层for遍历背包,内层for循环遍历物品

而本题字符串里单词的出现是由顺序的,所以我们求得是排列数。

举例dp

代码

本题还有个旧知识新用,就是把词典数组做个set哈希表,方便查找某单词是否出现在其中。

class Solution {
public:
    bool wordBreak(string s, vector<string>& wordDict) {
        vector<int>dp(s.size()+1,0);
        dp[0]=1;
        unordered_set<string>wordset(wordDict.begin(),wordDict.end());//使用 unordered_set 来存储字典中的单词,方便快速查找。
        for(int i=1;i<=s.size();i++)//背包
        {
            for(int j=0;j<i;j++)//物品,内层循环 j 从 0 到 i-1,用于检查从位置 j 到 i-1 的子字符串。
            {
                string word=s.substr(j,i-j);//!!一个个把(j,i-j)的字符串加入到word中,直到确认存在词典中
                if(wordset.find(word)!=wordset.end()&&dp[j]==true)//如果该子字符串存在于字典中(通过 wordset.find(word) != wordset.end()),并且 dp[j] 为 true(表示前 j 个字符可以被拆分),则我们可以将 dp[i] 设置为 true。
                dp[i]=true;
            }

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

打印dp数组

假设我们有以下输入:

  • 字符串 : s = "leetcode"
  • 字典 : wordDict = ["leet", "code"]
初始化 dp 数组

初始的 dp 数组为:

dp = [1, 0, 0, 0, 0, 0, 0, 0, 0] // dp[0] = 1
填充 dp 数组

逐步遍历并更新 dp 数组的过程如下:

  1. i = 1:

    • j = 0: word = "l",不在字典中,继续。
    • dp[1] = 0
  2. i = 2:

    • j = 0: word = "le",不在字典中。
    • j = 1: word = "e",不在字典中。
    • dp[2] = 0
  3. i = 3:

    • j = 0: word = "lee",不在字典中。
    • j = 1: word = "e",不在字典中。
    • j = 2: word = "e",不在字典中。
    • dp[3] = 0
  4. i = 4:

    • j = 0: word = "leet",在字典中,且 dp[0] = 1
    • 更新 dp[4] = 1
  5. i = 5:

    • j = 0: word = "leetc",不在字典中。
    • j = 1: word = "eec",不在字典中。
    • j = 2: word = "ec",不在字典中。
    • j = 3: word = "c",不在字典中。
    • dp[5] = 0
  6. i = 6:

    • j = 0: word = "leetco",不在字典中。
    • j = 1: word = "etco",不在字典中。
    • j = 2: word = "et",不在字典中。
    • j = 3: word = "c",不在字典中。
    • j = 4: word = "co",不在字典中。
    • dp[6] = 0
  7. i = 7:

    • j = 0: word = "leetcod",不在字典中。
    • j = 1: word = "etcod",不在字典中。
    • j = 2: word = "etc",不在字典中。
    • j = 3: word = "c",不在字典中。
    • j = 4: word = "co",不在字典中。
    • j = 5: word = "o",不在字典中。
    • dp[7] = 0
  8. i = 8:

    • j = 0: word = "leetcode",不在字典中。
    • j = 1: word = "eetcode",不在字典中。
    • j = 2: word = "etc",不在字典中。
    • j = 3: word = "c",不在字典中。
    • j = 4: word = "co",不在字典中。
    • j = 5: word = "o",不在字典中。
    • j = 6: word = "e",不在字典中。
    • dp[8] = 0

最终 dp 数组为:

dp = [1, 0, 0, 0, 1, 0, 0, 0, 1]

小小总结,完全背包的排序问题所求涉及三方面:组合数,排列数,最小数。

背包总结篇如图

多重背包理论基础

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

多重背包和01背包是非常像的, 为什么和01背包像呢?

每件物品最多有Mi件可用,把Mi件摊开,其实就是一个01背包问题了。

例如:

背包最大重量为10。

物品为:

重量 价值 数量
物品0 1 15 2
物品1 3 20 3
物品2 4 30 2

问背包能背的物品最大价值是多少?

和如下情况有区别么?

重量 价值 数量
物品0 1 15 1
物品0 1 15 1
物品1 3 20 1
物品1 3 20 1
物品1 3 20 1
物品2 4 30 1
物品2 4 30 1

毫无区别,这就转成了一个01背包问题了,且每个物品只用一次。

相关推荐
一直学习永不止步1 小时前
LeetCode题练习与总结:最长回文串--409
java·数据结构·算法·leetcode·字符串·贪心·哈希表
Rstln2 小时前
【DP】个人练习-Leetcode-2019. The Score of Students Solving Math Expression
算法·leetcode·职场和发展
珹洺2 小时前
C语言数据结构——详细讲解 双链表
c语言·开发语言·网络·数据结构·c++·算法·leetcode
几窗花鸢2 小时前
力扣面试经典 150(下)
数据结构·c++·算法·leetcode
迷迭所归处3 小时前
动态规划 —— 子数组系列-单词拆分
算法·动态规划
Lenyiin7 小时前
02.06、回文链表
数据结构·leetcode·链表
烦躁的大鼻嘎7 小时前
模拟算法实例讲解:从理论到实践的编程之旅
数据结构·c++·算法·leetcode
祁思妙想8 小时前
10.《滑动窗口篇》---②长度最小的子数组(中等)
leetcode·哈希算法
alphaTao9 小时前
LeetCode 每日一题 2024/11/18-2024/11/24
算法·leetcode
kitesxian9 小时前
Leetcode448. 找到所有数组中消失的数字(HOT100)+Leetcode139. 单词拆分(HOT100)
数据结构·算法·leetcode