代码随想录算法训练营Day44|322.零钱兑换、279.完全平方数、139.单词拆分

零钱兑换

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

本题是完全背包问题

dp数组表示组成amount金额所需的最少硬币个数。

考虑dp数组的推导公式,由于是计算最少硬币的个数,所以需要考虑dp[i-coins[j]+1和dp[i]的较小值。所以dp[i] = min(dp[i-coins[j]]+1,dp[i]),其中i为遍历过程中的amout值,coins[j]为硬币的面值。

已知推导公式,我们需要对dp数组赋值,由于dp推导式中求的是较小值,所以我们设定dp[0] = 0,其余值都为INT_MAX。

之后是对dp数组的遍历顺序,这里由于我们考虑的是最少银币个数,并不在乎排列或是组合的情况(组成的数目),所以对背包或是物品进行遍历都是可以的,这里我使用先背包后物品的遍历方式。

class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        // 创建一个动态规划数组dp,大小为amount+1,初始化为INT_MAX(表示无法凑成的金额)
        vector<int>dp(amount+1,INT_MAX);
        // 找零0元需要0个硬币
        dp[0] = 0;
        // 遍历从1到amount的每一个金额
        for(int i = 1; i<=amount;i++){
            // 遍历每一种硬币
            for(int j = 0;j<coins.size();j++){
                // 如果当前金额大于或等于当前硬币面额,并且当前金额减去当前硬币面额的找零方式存在
                if(i-coins[j]>=0 and dp[i-coins[j]]!=INT_MAX){
                    // 更新当前金额的最少硬币数量为min(当前最少硬币数量, 减去当前硬币面额的金额的硬币数量+1)
                    dp[i] = min(dp[i],dp[i-coins[j]]+1);
                }
            }
        }
        // 如果amount的找零方式不存在,返回-1
        if(dp[amount] == INT_MAX)return -1;
        // 返回amount的最少硬币找零数量
        return dp[amount];
    }
};

算法的时间复杂度为O(n*m),n为coins数组的长度,m为amount+1,空间复杂度为O(m),需要维护一个dp数组,长度为amount+1.

完全平方数

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

感觉本题和上题比较类似,唯一的不同在于coins数组需要我们自己获取。

dp数组定义等都和上题相同。

class Solution {
public:
    int numSquares(int n) {
        // 创建一个动态规划数组dp,大小为n+1,初始化为INT_MAX(表示无法组成的情况)
        vector<int> dp(n+1, INT_MAX);
        // 创建一个数组T_S_N,用于存储小于等于n的所有完全平方数
        vector<int> T_S_N; // total Square numbers
        // 计算小于等于n的所有完全平方数并存储到T_S_N中
        for (int i = 1; i * i <= n; i++) {
            T_S_N.push_back(i * i);
        }
        // 组成0需要0个完全平方数
        dp[0] = 0;
        // 遍历从1到n的每一个金额
        for (int i = 1; i <= n; i++) {
            // 遍历每一种完全平方数
            for (int j = 0; j < T_S_N.size(); j++) {
                // 如果当前数值大于或等于当前完全平方数,并且当前数值减去当前完全平方数的组成方式存在
                if (i - T_S_N[j] >= 0 && dp[i - T_S_N[j]] != INT_MAX) {
                    // 更新当前数值的最少完全平方数数量为min(当前最少完全平方数数量, 减去当前完全平方数的金额的完全平方数数量+1)
                    dp[i] = min(dp[i - T_S_N[j]] + 1, dp[i]);
                }
            }
        }
        // 返回n的最少完全平方数组成数量
        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]);
            }
        }
        return dp[n];
    }
};

算法的时间复杂度为O(n*(3/2)),空间复杂度为O(n)。

单词拆分

139. 单词拆分 - 力扣(LeetCode)

具体参考代码随想录 代码随想录 (programmercarl.com)

考虑单词为物品,所要匹配的字符串为背包,单词可以重复使用,就是一个使用单词匹配字符串(单词是否能完全构成字符串)的完全背包问题。

这里我们考虑将单词数组存入哈希集合,因为可以方便快速寻找

dp[i]中i表示字符串的长度,dp[i]表示为是否可以拆分为单词数组中的单词,值为true 或false.

dp[i]的递推公式我们应这样考虑,若dp[before_i]为true,且before_i至i的位置的字符串存在于单词数组中,则dp[i]为true,否则为false。

考虑到dp[i]取决于前面的值,则dp[0] = true,否则后续值递推全为false。

遍历方式:这里需注意应为排列,每个单词元素的顺序是有意义的。因此考虑先背包后物品的遍历方式

最后返回数组的末尾元素即知道拆分是否可能实现。

class Solution {
public:
    bool wordBreak(string s, vector<string>& wordDict) {
        // 创建一个哈希集合word_set,用于存储wordDict中的所有单词,以便快速查找
        unordered_set<string> word_set{};
        // 将wordDict中的所有单词插入到word_set中
        for (auto word : wordDict) {
            word_set.insert(word);
        }
        // 创建一个动态规划数组dp,大小为s.size()+1,初始化为false
        // dp[i]表示字符串s的前i个字符是否可以被拆分成wordDict中的单词
        vector<bool> dp(s.size() + 1, false);
        // 初始化dp[0]为true,因为空字符串可以被拆分成空集合
        dp[0] = true;
        // 遍历字符串s的每一个位置
        for (int i = 1; i <= s.size(); i++) {
            // 对于每个位置i,尝试从0到i的所有分割点j
            for (int j = 0; j < i; j++) {
                // 取出从j到i的子串
                string word = s.substr(j, i - j);
                // 如果子串word在word_set中,并且dp[j]为true(前j个字符可以拆分)
                if (word_set.find(word) != word_set.end() && dp[j]) {
                    // 则dp[i]为true,表示前i个字符可以拆分
                    dp[i] = true;
                }
            }
        }
        // 返回dp[s.size()]
        return dp[s.size()];
    }
}; 
  • 时间复杂度:O(n^3),因为substr返回子串的副本是O(n)的复杂度(这里的n是substring的长度)
  • 空间复杂度:O(n)

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值