代码随想录动态规划06

322. 零钱兑换

如果求组合数就是外层for循环遍历物品,内层for遍历背包。

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

视频讲解:动态规划之完全背包,装满背包最少的物品件数是多少?| LeetCode:322.零钱兑换_哔哩哔哩_bilibili

代码随想录

给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。

计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。

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

class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        vector<int>dp(amount+1,amount+1);//含义dp[n]凑成n的最小硬币数
        dp[0]=0;
        for(int i=0;i<coins.size();i++ )
        {
            for(int j=coins[i];j<=amount;j++)
            {
                dp[j]=min(dp[j],dp[j-coins[i]]+1);
            }
        }
return dp[amount]==amount+1?-1:dp[amount];

    }
};
  1. 遍历顺序问题

本题求钱币最小个数,那么钱币有顺序和没有顺序都可以,都不影响钱币的最小个数

所以本题并不强调集合是组合还是排列。

如果求组合数就是外层for循环遍历物品,内层for遍历背包

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

所以本题的两个for循环的关系是:外层for循环遍历物品,内层for遍历背包或者外层for遍历背包,内层for循环遍历物品都是可以的!

那么我采用coins放在外循环,target在内循环的方式。

本题钱币数量可以无限使用,那么是完全背包。所以遍历的内循环是正序

综上所述,遍历顺序为:coins(物品)放在外循环,target(背包)在内循环。且内循环正序。

// 版本二
class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        vector<int> dp(amount + 1, INT_MAX);
        dp[0] = 0;
        for (int i = 1; i <= amount; i++) {  // 遍历背包
            for (int j = 0; j < coins.size(); j++) { // 遍历物品
                if (i - coins[j] >= 0 && dp[i - coins[j]] != INT_MAX ) {
                    dp[i] = min(dp[i - coins[j]] + 1, dp[i]);
                }
            }
        }
        if (dp[amount] == INT_MAX) return -1;
        return dp[amount];
    }
};

因为是求最小的硬币数,所以可以先初始化为最大值

279.完全平方数

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

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

示例 1:

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

视频讲解:动态规划之完全背包,换汤不换药!| LeetCode:279.完全平方数_哔哩哔哩_bilibili

代码随想录

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++)//完全背包问题,正序,第一个数为完全平方数数组的第一个数,L例如coins[i]
            {
                dp[j]=min(dp[j],dp[j-i*i]+1);
            }
        }
        return dp[n];
    }
};

注意点:dp[0]=0,dp[0] = 0 的初始化是 动态规划的一种惯例这个初始化原则在很多动态规划问题中都适用(比如零钱兑换、背包问题等)。

数学上的解释

如果 dp[0] 是“无数个 0”,那么最小数量是多少?最小数量仍然是 0,因为我们 不需要任何数字 来构造 0。

2.完全背包正序,j=的第一个数为数组的第一个要遍历的数

3.i*i<=n,不能漏掉任何一个完全平方数

139.单词拆分

视频讲解:动态规划之完全背包,你的背包如何装满?| LeetCode:139.单词拆分_哔哩哔哩_bilibili

代码随想录

给定一个非空字符串 s 和一个包含非空单词的列表 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。

说明:

拆分时可以重复使用字典中的单词。

你可以假设字典中没有重复的单词。

示例 1:

  • 输入: s = "leetcode", wordDict = ["leet", "code"]
  • 输出: true
  • 解释: 返回 true 因为 "leetcode" 可以被拆分成 "leet code"。

示例 3:

  • 输入: s = "catsandog", wordDict = ["cats", "dog", "sand", "and", "cat"]
  • 输出: false

复习:unordered_set(哈希集合) 基本操作

  1. 包含头文件: 要使用 unordered_set,你需要包含头文件 #include <unordered_set>

  2. 声明和初始化

    unordered_set<int> s; // 创建一个空的 unordered_set
    unordered_set<int> s = {1, 2, 3, 4}; // 初始化时直接插入元素
    unordered_set<string> wordSet = {"apple", "banana", "cherry"};
    

    unordered_set 特点

  3. 无序性unordered_set 中的元素是无序的,和 set 中的元素不同,unordered_set 并不保证元素的顺序。其元素在内存中的排列顺序是由哈希表决定的。

  4. 哈希表实现unordered_set 使用哈希表来存储数据,因此它的查找、插入和删除操作的平均时间复杂度为 O(1)。若哈希函数不太好,造成的哈希冲突太多,时间复杂度可能为O(n)

  5. 元素唯一性:和 set 一样,unordered_set 只会保存唯一的元素,重复插入相同的元素不会改变容器内容。

  6. 底层哈希函数:C++ 默认为一些常见类型(如 int, string 等)提供了哈希函数,但对于自定义类型,你需要提供一个哈希函数。

  7. 字符串基本操作

    substr(j, i - j) 是 C++ 的字符串方法,用来从位置 j 开始,提取长度i - j 的子字符串。也就是我们从位置 j 开始,到位置 i 的子字符串 s[j:i]。例如,假设 s = "applepen",那么对于 i = 5j = 0word = "apple"

  • 外层循环 i 是在逐步增加背包的容量,也就是检查目标字符串的不同子串。

  • 内层循环 j 是遍历所有可能的“分割点”,从 0i-1,通过 s[j:i] 截取子字符串来检查它是否在字典中。

  • 当满足条件时,更新 dp[i],表示从 0i 的子字符串可以用字典中的单词组成。

此题注意点:

1.找准背包是什么,物品是什么

  • 背包:就是当前要考虑的字符串 s[0:i],它是我们要填充的目标。

  • 物品:并不是字典中的单词本身,而是通过从 s 中截取的子字符串 s[j:i],这些子字符串可能恰好是字典中的某个单词。

2.一定要考虑遍历顺序,字符串的组成是有顺序的,先遍历背包,在遍历物品

3.dp[0]=true;注意初始化

4.使用哈希表储存可以降低时间复杂度O(1),因为每一个子字符串都需要去查找,可降低时间复杂度

5.第一个for循环代表背包起始容量,其实容量最小为1,等同于我需要考察从0开始,长度为1的字符串

6.第二个for循环表示在(0,i)的字符串中拆分物品,相当于枚举,每一个拆分出的子字符串在字典中一一考察

7.dp[j]的判断

  1. 如果这两个条件都成立,说明我们可以将 s[0:j]s[j:i] 连接起来,组成 s[0:i]。因此,我们可以将 dp[i] 设置为 true,表示 s[0:i] 也能通过字典中的单词拼接成。

  2. dp[j]true:表示从 s[0]s[j] 的子字符串能够由字典单词拼接而成。

    • wordSet.find(cmp) != wordSet.end():表示 s[j:i] 这段子字符串是字典中的单词。

举个例子:假设我们有:
  • s = "leetcode"

  • wordDict = ["leet", "code"]

目标:判断 s = "leetcode" 是否可以由字典中的单词 "leet""code" 拼接而成。

  • 初始时:dp = [true, false, false, false, false, false, false, false, false](表示 s[0:0] 空字符串可以拼接)

  • 当我们检查 i = 4 时:

    • j = 0cmp = "leet"cmp 在字典中,并且 dp[0]true(表示 s[0:0] 是有效的)。

    • 所以 dp[4] = true

  • 当我们检查 i = 8 时:

    • j = 4cmp = "code"cmp 在字典中,并且 dp[4]true(表示 s[0:4] 是有效的)。

    • 所以 dp[8] = true,意味着 s[0:8] 可以通过字典中的单词 "leet""code" 拼接而成

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[n]含义3,表示s[0,n]的字符串能在字典中找到
        dp[0]=true;
        //然后考虑遍历顺序,组合还是排列,先遍历谁
        //最后字符串的组成是按一定顺序的,排列dp
        for(int i=1;i<=s.size();i++)//先选背包,逐步扩大背包的容量
        {
            for(int j=0;j<i;j++)//后面选物品,s[j:i]为一个物品,就是通过分割目标字符串形成的物品
            {
                string cmp=s.substr(j,i-j);//i-j为截取的长度,此时的cmp为截取的物品
                if(wordSet.find(cmp)!=wordSet.end()&&dp[j])///字典仅仅是一个查找工具,里面的每一个字符串不是物品
                {
                 dp[i]=true;//记忆化累计
                }

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

与上一个完全背包问题,排列dp问题相比较

class Solution {
public:
    int combinationSum4(vector<int>& nums, int target) {
        vector<long long>dp(target+1,0);
        dp[0]=1;//组合个数,什么都不选
        for(int i=1;i<=target;i++)
        {
            for(int j=0;j<nums.size();j++)
            {
                if(i>=nums[j]&&(dp[i]+dp[i-nums[j]])<INT_MAX)
               { dp[i]+=dp[i-nums[j]];}
        }}
return dp[target];
    }
};

用java编写

class Solution {
    public boolean wordBreak(String s, List<String> wordDict) {
        boolean [] dp=new boolean[s.length()+1];//创建dp数组,已经默认初始化为false
        dp[0]=true;
        HashSet<String>set=new HashSet<>(wordDict);
        for(int i=1;i<=s.length();i++)//表示背包的容量
        {
            for(int j=0;j<i;j++)//遍历物品
            if(set.contains(s.substring(j,i))&&dp[j])
            {
                dp[i]=true;//先遍历背包,应该先更新i
            }
        }
return dp[s.length()];
    }
}

注意  substring的用法

contains

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值