目录

刷leetcode-hot100-动态规划3.10

刷leetcode hot100–动态规划3.10

继续昨天的完全平方数[13:20]【确实和零钱兑换一脉相承】

前提简要:昨天尝试,没什么想法,本来觉得maybe先求一下sqrt(n),确定一下阈值【其实也对】

然后不知道怎么求sqrt了??–>直接调用sqrt(n) 或者 用二分查找


进入正题:

dp[n]表示和为n的完全平方数最小数量

dp[n] = min{dp[n-i*i]+1,dp[n]}

初始化 dp[n]–>INT_MAX

第一版代码问题【编译不过】:1. dp[0]=0 ,要不然dp[1]没法是1, 所以还是要带入代码验算几个dp, dp不能全初始化为INT_MAX/0,要不然没法迭代

2.j<=sqrt(i),"<=",要不然1没法遍历到完全平方数1;“sqrt(i)”防止i-j*j<0,数组索引不合法

if (dp[i - j * j] != INT_MAX) 防止INT_MAX+1溢出【其实也就是 j <= sqrt(i)】

class Solution {
public:
    int numSquares(int n) {
        // 完全想不到和动态规划什么关系
        // 完全平方数,如何开方
        // 和为n的完全平方数最小数量-->dp[n]表示和为n的完全平方数最小数量
        // dp[n] = min{dp[n-i*i]+1,dp[n]}但是不会超出时间限制吗
        // ?初始化
        vector<int> dp(n + 1, INT_MAX);
        dp[0] = 0;
        // 遍历
        for (int i = 1; i <= n; i++) {           // dp[n]
            for (int j = 1; j <= sqrt(i); j++) { // j^2 <=e.g.1

               // if (dp[i - j * j] != INT_MAX) {
                    dp[i] = min(dp[i - j * j] + 1, dp[i]);
                //}
            }
        }

        return dp[n];
    }
};

第二题:单词拆分

【回溯:分隔回文串???】

回顾:c++中关于字符串的函数

1.s1.substr(2,5); // 结果:23456—–参数2:从s1[2]开始截取,参数5表示:截取多长

s.end() 适用于 标准容器(如 std::stringstd::vector )或支持 end() 成员函数的自定义类

set的find返回的也是迭代器,string的find返回的是string首字母下标

题的思路:1.dp[n]表示前n个字符是否可以用已知wordDict表示

2.初始化:dp[0] = true,其他false

3.遍历顺序:排列数–>先遍历背包,再遍历物品【我的初始循环i,更新                                        i+wordDict[j].size()-1的dp,这个思路比较混乱,虽然也能行的通;

一般思路是更新dp[i]】

初版代码问题:

卒于s =“abcd”;wordDict =[“a”,“abc”,“b”,“cd”]

输出1,3,2,

好吧,为什么呢 ,不理解

我悟了!!!!!

for(int j = 0; j<wordDict.size() && i+wordDict[j].size()-1<=s.size() ;j++){

j<wordDict.size() && i+wordDict[j].size()-1<=s.size()

如果for中某一时刻不满足这个条件,for结束循环,退出,即j=1不满足的话,就不会有j=2了

所以正确的做法是:

for(int j = 0; j < wordDict.size(); j++) {

if ((i + wordDict[j].size() - 1) <= s.size()) {

这告诉我们,for的条件里不要塞太多。。。

class Solution {
public:
    bool wordBreak(string s, vector<string>& wordDict) {
        //字符串操作
        //依旧不知道和动态规划的关系
        //看代码随想录说想成背包【s】和物品【wordDict】
        //难道不是检验wordDict有没有和s重合的,然后去除,检测重合-->for{for{}} OR s.find("");
        //dp[n]表示从本字符串从第一个字符到第n个是否可以由这些单词组成
        vector<bool> dp(s.size()+1,false);//bool
        dp[0] = true;
        //dp[n] = dp[i] && s[i...wordDict[j].size()+i-1].find(wordDict[j])
        for(int i = 1;i<=s.size();i++){//第一个字符下标为0
            for(int j = 0;j<wordDict.size() && i+wordDict[j].size()-1<=s.size() ;j++){
                string sub = s.substr(i-1,wordDict[j].size());
                if(sub == wordDict[j]){
                    if(dp[i-1] == true){
                        dp[wordDict[j].size()+i-1] = true;
                        cout<<wordDict[j].size()+i-1<<endl;
                    }
                }
            }
        }
        return dp[s.size()];
        
    }
};

比较正常的思路。。

一般dp[n]遍历到n是更新的是dp[n],要不然逻辑太混乱

在实操过程中,i/j/wordDict[j].size()我一直绕不清楚,太绕了……

如果顺应初始化dp(size(),false)会好一点

class Solution {
public:
    bool wordBreak(string s, vector<string>& wordDict) {
        //字符串操作
        //依旧不知道和动态规划的关系
        //看代码随想录说想成背包【s】和物品【wordDict】
        //难道不是检验wordDict有没有和s重合的,然后去除,检测重合-->for{for{}} OR s.find("");
        //dp[n]表示从本字符串从第一个字符到第n个是否可以由这些单词组成
        vector<bool> dp(s.size()+1,false);//bool
        dp[0] = true;
        unordered_set<string> wordSet(wordDict.begin(), wordDict.end());
        //dp[n] = dp[i] && s[i...wordDict[j].size()+i-1].find(wordDict[j])
        for(int i = 1;i<=s.size();i++){//第一个字符下标为0
            for(int j = 1;j<=i;j++){
                //j-1..i-1
               string word = s.substr(j-1, i - j +1); 
                if (wordSet.find(word) != wordSet.end() && dp[j-1]) {
                    dp[i] = true;
                }
            }
        }
        return dp[s.size()];
        
    }
};