动态规划-----JZ67,JZ52、面试题46、47、48、49、60、63

JZ67剪绳子

题目描述
给你一根长度为n的绳子,请把绳子剪成整数长的m段(m、n都是整数,n>1并且m>1,m<=n),每段绳子的长度记为k[1],…,k[m]。请问k[1]x…xk[m]可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。

输入描述:
输入一个数n,意义见题面。(2 <= n <= 60)
返回值描述:
输出答案。

示例1
输入
8
返回值
18

预备知识
动态规划(Dynamic programming)题的特点:
1、求最优解;
2、整体问题的最优解依赖各个子问题的最优解;
3、该问题能被分解成若干小问题,且小问题之间还有相互重叠的更小子问题;
4、问题需要从上向下分析(这样会有重叠的子问题),但可以从下向上求解来避免重复计算。
5、
在这里插入图片描述

题解:
在这里插入图片描述
又有f(2)=1,f(3)=2-----------注:n>1,m>1即绳子总长最短是2,最少切成2段(即最少切一刀,必须切一刀

模式识别:
1、题目求最值---------最优解;
2、f(n)=max(f(i)*f(n-i))----------可分解成若干子问题;
3、在这里插入图片描述
如图,子问题间有重复部分;

所以使用动态规划,使用从下向上求解来避免重复计算。

代码:

class Solution {
public:
    int cutRope(int number) {
        if(number<2)
            return 0;
        if(number==2)
            return 1;
        if(number==3)
            return 2;
        vector<int> f(number+1,-1);
        for(int n=0;n<=3;n++){
            f[n]=n;//前3个不按f(2)=1,f(3)=2赋值,是因为当长度大于3时,切出的一段长度为2或3的绳子,本身的长度值比f(2),f(3)大,所以计算f(n),n>3时,不能用f(2)=1,f(3)=2;
        }
        for(int n=4;n<=number;n++){
            for(int i=1;i<=n/2;i++){//i<=n/2避免重复计算;
                f[n]=max(f[n],f[i]*f[n-i]);
            }
        }
        return f[number];
    }
};

时间复杂度:O(n^2),两个for循环。

空间复杂度:O(n),创建了一个数组vector<int> f(number+1,-1);

标准动态规划(Dynamic programming)解法

1、定义状态dp[n]:表示将长度为n的绳子剪成若干段后,各段乘积的最大值。
2、边界条件(初始状态): 要求n>1,m>1,即绳子最短为2且最少剪一刀,0 和 1 都不能剪,因此 dp[0]=dp[1]=0。
3、状态转移方程:
当 n≥2 时,在剪第一刀的时候,我们有n-1种可能的选择,也就是剪出来的第一段绳子可能的长度为i=1,2,…,n-1。则dp[n]有以下两种情况:

将n 拆分成 i 和 n−i 的和,且 n−i 不再剪成若干段,此时的乘积是 i×(n−i);
将n 拆分成 i 和 n−i 的和,且 n−i 继续剪成若干段,此时的乘积是 i×dp[n−i]。
注:之所以区分 n−i 继不继续剪成若干段,是因为不能确定(n−i)和dp[n−i]的大小,比如n=2>dp(2)=1;n=3>dp[3]=2;但n=5<dp[5]=6 (n>=4时,n<dp[n])

因此,当 i 固定时,有 dp[n]=max(i×(n−i),i×dp[n−i])。由于 i 的取值范围是 1 到 n−1,需要遍历所有的 i 得到 dp[n] 的最大值,因此可以得到状态转移方程如下:

在这里插入图片描述
代码:

class Solution {
public:
    int cutRope(int number) {
       vector<int> dp(number+1,0);
        for (int n = 2; n <= number; n++) {
            for (int i = 1; i <= n-1; i++) {
                dp[n]= max(dp[n], max(i * (n - i), i * dp[n - i]));
            }
        }
        return dp[number];
    }
};

优化
根据 (n>=4时,n<dp[n])进行优化时间效率
代码:

class Solution {
public:
    int cutRope(int number) {
       vector<int> dp(number+1,-1);
        for(int n=0;n<=3;n++){
            dp[n]=n;
        }
        for (int n = 4; n <= number; n++) {
            for (int i = 1; i <= n/2; i++) {// i <= n/2 --->第二个for循环少循环一半
                dp[n]= max(dp[n], dp[i] * dp[n - i]);
            }
        }
        return dp[number];
    }
};

JZ52正则表达式匹配

参考

代码:

class Solution {
    public boolean isMatch(String s, String p) {
        int sLength = s.length();
        int pLength = p.length();
        int[][] dp = new int[sLength + 1][pLength + 1];
        for(int i = 0; i <= sLength; i++){
            for(int j = 0; j <= pLength; j++){
                if(j == 0){
                    dp[i][j] = (i ==0? 1 : 0);
                    //i == 0? dp[i][j] = 1 : dp[i][j] = 0;
                }else{
                    if(p.charAt(j-1) != '*'){
                        if(i >= 1 && (s.charAt(i-1) == p.charAt(j-1) || p.charAt(j-1) == '.')){
                        dp[i][j] = dp[i-1][j-1];
                         }
                    }else{
                        if(j >= 2){
                            dp[i][j] |= dp[i][j-2];
                        }
                        if(i >= 1 && j>=2 && (s.charAt(i-1) == p.charAt(j-2) || p.charAt(j-2)=='.')){
                            dp[i][j] |= dp[i-1][j];
                        }
                    }
                }
            }
        }
        return dp[sLength][pLength] == 1;
    }
}

注:
关于为什么用|=,比如这段代码:

if (j >= 2) {
    f[i][j] |= f[i][j - 2]; //可用可不用,因为dp矩阵初始化默认为false,本质上和=一样
}
//看
if (i >= 1 && j >= 2 && (A.charAt(i - 1) == B.charAt(j - 2) || B.charAt(j - 2) == '.')) {
    f[i][j] |= f[i - 1][j]; //必须使用,否则不能ac
} 

其中,第一步先算的是不看‘’的情况,然后第二步再算看‘’的情况。也就是说,对于f[i][j]我们会算两次。如果在第一次,即不看’‘的时候,就已经算出来TURE了。那在第二步看’'的时候。不管结果是ture还是false,都保持true不变,这是合理的,因为只要其中有一种情况能完整匹配,结果就为true。这就是为什么要用或符号。 这个不难证明,举个例子 “ba” “baa*” 这种情况下直接用=号过不了。

JZ30 连续子数组的最大和

题目描述
输入一个整型数组,数组里有正数也有负数。数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。要求时间复杂度为 O(n).

输入
[1,-2,3,10,-4,7,2,-5]
返回值
18
说明
输入的数组为{1,-2,3,10,4,7,2,5},和最大的子数组为{3,10,4,7,2},因此输出为该子数组的和 18

方法一:动态规划

题解:
模式识别——该问题是个求最优解的问题,所以考虑用动态规划。【且题目要求时间复杂度为O(n),动态规划又是用for循环实现,所以可判断只需用一层for循环,题不算复杂】

1、定义状态dp[i]:代表以元素 array[i] 为结尾的连续子数组最大和。
2、边界条件(初始状态):i>=0,从数组的下标0处开始,dp[0] = array[0],即以 array[0]结尾的连续子数组最大和为 array[0] 。
3、状态转移方程
dp[i] = max(array[i], dp[i-1]+array[i])
解释
-----当dp[i-1]<=0时,dp[i-1]对dp[i]产生负作用或不起作用,即,dp[i-1]+ array[i] <=array[i],所以dp[i]=array[i];
-----当dp[i-1]>0时,dp[i-1]+ array[i] >array[i],所以dp[i]=dp[i-1]+ array[i];
所以dp[i] = max(array[i], dp[i-1]+array[i])

代码:

class Solution {
public:
    int FindGreatestSumOfSubArray(vector<int> array) {
        int sz=array.size();
        vector<int> dp(sz,0);
        dp[0]=array[0];
        int ret=dp[0];
        for(int i=1;i<array.size();i++){
            dp[i]=max(dp[i-1]+array[i], array[i]);
            ret=max(ret,dp[i]);
        }
        return ret;
    }
};

时间复杂度:O(n),一次for循环线性遍历数组 。
空间复杂度:O(n),vector<int> dp(sz,0);

方法二:分析数组规律
一边遍历一边保存替换最大和
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

class Solution {
public:
    int FindGreatestSumOfSubArray(vector<int> array) {
        int curSum=0;
        int ret=array[0];
        for(int i=0;i<array.size();i++){
            if(curSum<=0) curSum=array[i];
            else curSum+=array[i];
            ret=max(curSum, ret);
        }
        return ret;
    }
};

时间复杂度:O(n)
空间复杂度:O(1)

面试题46 把数字翻译成字符串

给定一个数字,我们按照如下规则把它翻译为字符串:0 翻译成 “a” ,1 翻译成 “b”,……,11 翻译成 “l”,……,25 翻译成 “z”。一个数字可能有多个翻译。请编程实现一个函数,用来计算一个数字有多少种不同的翻译方法。

 示例 1:

输入: 12258
输出: 5
解释: 122585种不同的翻译,分别是"bccfi", "bwfi", "bczi", "mcfi""mzi"

题解:
模式识别——本题是计算一个数字有多少种不同的翻译方法,也就是求最多有多少种翻译方法,即求最优解-------动态规划问题。

记num为
在这里插入图片描述

1、定义状态dp[i]:dp[i]代表以第i个数字(x_i)为结尾的数字的翻译方案数量。
2、状态转移方程
第i个数字(x_i)第i-1个数字(x_i-1)组成的两位数字可以被翻译,则 dp[i] = dp[i - 1] + dp[i - 2];否则 dp[i] = dp[i - 1] 。

"z"对应的数字是25,所以大于25的两位数不能被翻译;两个连续数字以0开头不在题目规定的翻译规则中,所以小于10的两位数不能被翻译,即x_i和 x_i−1组成的两位数字可以被翻译的范围是[10, 25]

3、边界条件(初始状态):
dp[0]=dp[1]=1 ,即 “无数字” 和 “第 1 位数字” 的翻译方法数量均为 1 ;

Q: 无数字情况 dp[0] = 1 从何而来?
A: 当 num 第 1, 2位的组成的数字∈[10,25] 时,显然应有 2 种翻译方法,即 dp[2] = dp[1] + dp[0] =2 ,而显然 dp[1] = 1 ,因此推出 dp[0] = 1 。
这里dp[0] 是被推导出来的,没有具体意义,dp[0] 会被后面的dp[i]用到,经推导dp[0]=1是合理的。

代码:

class Solution {
public:
    int translateNum(int num) {
        string store=to_string(num);
        int sz=store.size();
        vector<int> dp(sz+1);
        dp[0]=dp[1]=1;
        for(int i=2; i<=sz; i++){
            //注意:i是从1开始的,store的下标是从0开始的;所以第i个数字在store中的下标是i-1,所以第i-1个和第i个数字组成的子串的下标从i-2开始,即store.substr(i-2, 2);
            string temp=store.substr(i-2, 2);//substr用法见下面知识点2;
            if(temp>="10"&&temp<="25"){
                dp[i]=dp[i-1]+dp[i-2];
            }
            else{
                dp[i]=dp[i-1];
            }
        }
        return dp[sz];

    }
};

时间复杂度:O(n),一次for循环线性遍历字符串 。
空间复杂度:O(n),vector<int> dp(sz+1) 字符串store使用 O(N) 大小的额外空间。

优化空间复杂度
由于 dp[i] 只与 dp[i - 1]和dp[i−2] 有关,因此可使用三个变量 a, b,r 分别记录 dp[i−2] ,dp[i−1],dp[i] 三个变量交替前进即可。此方法可省去 dp列表使用的 O(N) 的额外空间。

class Solution {
public:
    int translateNum(int num) {
        string store=to_string(num);
        int sz=store.size();
        if(sz<2) return 1; //由于使用滚动数组,不在记录dp[0],dp[1]这些前面元素,只记录后面元素,又因为下面循环是从第2个元素开始的,所以num若只有一个元素,是进不去循环的,返回的ret也没经过操作复制,所以需先处理num若只有一个元素的情况;或者初始化ret=1
        int left=1, right=1;//开始时left=dp[0],right=dp[1];
        int ret;
        for(int i=2; i<=sz; i++){
            //注意:i是从1开始的,store的下标是从0开始的;所以第i个数字在store中的下标是i-1,所以第i-1个和第i个数字组成的子串的下标从i-2开始,即store.substr(i-2, 2);
            string temp=store.substr(i-2, 2);
            if(temp>="10"&&temp<="25"){
                ret=left+right;
            }
            else{
                ret=right;
            }
            left=right;
            right=ret;
        }
        return ret;

    }
};

时间复杂度:O(n),一次for循环线性遍历字符串 。
空间复杂度:O(n),字符串store使用 O(N) 大小的额外空间。

面试题47 礼物的最大价值

题目:
在一个 m*n 的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于 0)。你可以从棋盘的左上角开始拿格子里的礼物,并每次向右或者向下移动一格、直到到达棋盘的右下角。给定一个棋盘及其上面的礼物的价值,请计算你最多能拿到多少价值的礼物?

示例 1:

输入: 
[
  [1,3,1],
  [1,5,1],
  [4,2,1]
]
输出: 12
解释: 路径 13521 可以拿到最多价值的礼物

题解:
模式识别——本题求最多能拿到多少价值的礼物,即求最优解-------动态规划问题。

1、定义状态dp[i][j]:
代表从棋盘的左上角开始,到达单元格 (i,j)时能拿到礼物的最大累计价值
2、状态转移方程(下面1-4种情况列出了边界条件)
在这里插入图片描述

3、初始状态:
dp[0][0]=grid[0][0] ,即到达单元格 (0,0)时能拿到礼物的最大累计价值为 grid[0][0];

4、返回值:
dp[m-1][n-1],m, n 分别为矩阵的行高和列宽,即返回 dp 矩阵右下角元素。

(一)代码:

class Solution {
public:
    int maxValue(vector<vector<int>>& grid) {
        int rows=grid.size();
        int colums=grid[0].size();
        vector<vector<int>> dp(rows,vector<int>(colums));
        for(int i=0; i<rows; i++){
            for(int j=0; j<colums; j++){
                if(i==0&&j==0) dp[i][j]=grid[0][0];
                else if(i==0) dp[i][j]=dp[i][j-1]+grid[i][j];
                else if(j==0) dp[i][j]=dp[i-1][j]+grid[i][j];
                else dp[i][j]=max(dp[i-1][j], dp[i][j-1]) + grid[i][j];

            }
        }
        return dp[rows-1][colums-1];

    }
};

时间复杂度 O(MN): M, N 分别为矩阵行高、列宽;动态规划需遍历整个 grid矩阵,使用 O(MN)时间。
空间复杂度 O(MN):创建了 vector<vector<int>> dp(rows,vector<int>(colums));

(二)空间复杂度优化:
在这里插入图片描述
应用此方法可省去 dp 矩阵使用的额外空间,因此空间复杂度从 O(MN) 降至 O(1)。

代码:

class Solution {
public:
    int maxValue(vector<vector<int>>& grid) {
        int rows=grid.size();
        int colums=grid[0].size();
        for(int i=0; i<rows; i++){
            for(int j=0; j<colums; j++){
                if(i==0&&j==0) continue;
                else if(i==0) grid[i][j]=grid[i][j-1]+grid[i][j];
                else if(j==0) grid[i][j]=grid[i-1][j]+grid[i][j];
                else grid[i][j]=max(grid[i-1][j], grid[i][j-1]) + grid[i][j];

            }
        }
        return grid[rows-1][colums-1];

    }
};

时间复杂度 O(MN): M, N 分别为矩阵行高、列宽;动态规划需遍历整个 grid矩阵,使用 O(MN)时间。
**空间复杂度 O(1)。

(三)多开一行一列的空间简化边界条件,让代码更简洁()

要是对空间要求不是很严格的话,dp中很多地方都会运用到这种思想(多扩一个空间)简化边界条件,使边界问题不容易出错。
注意:此时,dp[i][j]表示从grid[0][0]到grid[i - 1][j - 1]时的最大价值

代码:

class Solution {
public:
    int maxValue(vector<vector<int>>& grid) {
        int rows=grid.size();
        int colums=grid[0].size();
        //dp[i][j]表示从grid[0][0]到grid[i - 1][j - 1]时的最大价值
        vector<vector<int>> dp(rows+1,vector<int>(colums+1,0));
        for(int i=1; i<=rows; i++){
            for(int j=1; j<=colums; j++){
               dp[i][j]=max(dp[i-1][j], dp[i][j-1]) + grid[i-1][j-1];

            }
        }
        return dp[rows][colums];

    }
};

时间复杂度 O(MN): M, N 分别为矩阵行高、列宽;动态规划需遍历整个 grid矩阵,使用 O(MN)时间。
空间复杂度 O((M+1)*(N+1)):创建了 vector<vector<int>> dp(rows+1,vector<int>(colums+1, 0));

面试题48 最长不含重复字符的子字符串

请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。

示例 1:

输入: "abcabcbb"
输出: 3 
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
示例 2:

输入: "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
示例 3:

输入: "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
     请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。

方法一 滑动窗口

方法二、动态规划
题解:
模式识别——本题求最长子字符串的长度,即求最优解-------动态规划问题。

1、定义状态dp[i]:
dp[i] 代表以字符 s[i]为结尾的 “最长不重复子字符串” 的长度。
2、状态转移方程
如果s[i]之前没出现过,那么dp[i]=dp[i]+1;

如果s[i]之前出现过:设字符 s[i]左边距离最近的相同字符为 s[j],他们之间的距离d=i - j
①若d<=dp[i - 1] ;字符 s[j]在子字符串 dp[i-1]区间之内,则dp[i]=d;
②若d>dp[i - 1] ;字符 s[j]在子字符串 dp[i-1]区间之外,则dp[i]=dp[i-1]+1;
3、初始状态:
dp[0]=1

4、返回值:
max(dp) ,即全局的 “最长不重复子字符串” 的长度。

空间复杂度优化:
由于返回值是取 dp 列表最大值,因此可借助变量 tmp存储 dp[i] ,变量 res 每轮更新最大值即可。
此优化可节省 dp 列表使用的 O(N)大小的额外空间。

代码:

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        map<char,int> hashmap;
        int res=0,temp=0;
        for(int i=0;i<s.size();i++){
            if(hashmap.find(s[i])==hashmap.end()){
                temp+=1;
                hashmap[s[i]]=i;
            }
            else{
                temp=i-hashmap[s[i]]<=temp? i-hashmap[s[i]] : temp+1;
                hashmap[s[i]]=i;
            }
            res=max(res,temp); //max(dp[j - 1], dp[j])
        }
        return res;

    }
};

-时间复杂度:O(n)。遍历字符串
-空间复杂度:O(m)。m为字符空间。
字符的 ASCII 码范围为 0 ~ 127,哈希表最多存m=128个字符,即使用O(m)= O(128) = O(1)大小的额外空间。

面试题49 丑数

我们把只包含质因子 2、3 和 5 的数称作丑数(Ugly Number)。求按从小到大的顺序的第 n 个丑数。

示例:

输入: n = 10
输出: 12
解释: 1, 2, 3, 4, 5, 6, 8, 9, 10, 12 是前 10 个丑数。
说明:  

1 是丑数。
n 不超过1690

方法一,暴力解法(超时,无法AC):
在这里插入图片描述
在这里插入图片描述

依据上述规律,判断每个数是不是丑数,直到丑数的数量达到 n。该方法会超时

代码:超时,无法AC

class Solution {
public:
    int nthUglyNumber(int n) {
        if (n <= 6) {return n;}

        int count = 6, i = 7;

        while (true) {
            if (determine(i)) {++ count;}
            if (count == n) {break;}
            ++ i;
        }

        return i;
    }
    bool determine(int num) {
        if (num > 0 && num <= 6) {return true;}
        else if (num <= 0) {return false;}

        while (num % 3 == 0) {num /= 3;}
        while (num % 5 == 0) {num /= 5;}
        while (num % 2 == 0) {num /= 2;}

        return (num == 1);
    }
};

方法二,动态规划:
题解

题解中 dp[i] 代表第 i + 1 个丑数;

下面代码为了直观,为dp数组多开了一个空间,改为dp[i] 代表第 i 个丑数
代码:

class Solution {
public:
    int nthUglyNumber(int n) {
        vector<int> dp(n+1);
        dp[0]=dp[1]=1;//这里dp[0]无实际意义
        int a=1,b=1,c=1;
        for(int i=2;i<=n;i++){
            dp[i]=min(min(2*dp[a], 3*dp[b]), 5*dp[c]);
            if(2*dp[a]==dp[i]) a++;
            if(3*dp[b]==dp[i]) b++;
            if(5*dp[c]==dp[i]) c++;
        }
        return dp[n];

    }
};

时间复杂度 O(N) : 其中 N = n ,动态规划需遍历计算 dp 列表。
空间复杂度 O(N) : 长度为 N 的 dp 列表使用 O(N) 的额外空间

在这里插入图片描述

面试题 60. n个骰子的点数

把n个骰子扔在地上,所有骰子朝上一面的点数之和为s。输入n,打印出s的所有可能的值出现的概率。

你需要用一个浮点数数组返回答案,其中第 i 个元素代表这 n 个骰子所能掷出的点数集合中第 i 小的那个的概率。

示例 1:

输入: 1
输出: [0.16667,0.16667,0.16667,0.16667,0.16667,0.16667]
示例 2:

输入: 2
输出: [0.02778,0.05556,0.08333,0.11111,0.13889,0.16667,0.13889,0.11111,0.08333,0

方法、动态规划
题解:

  • 投掷 n 个骰子,一共会有 6 的 n 次方 种结果,且每种结果都是等可能事件
  • 投掷 n 个骰子,那么就会有 n 个面朝上,这 n 个朝上的面的点数之和 s 的最大值是 6n,最小值是 n。故投掷 n 个骰子,s 一共有 6n - n + 1 个可能的值。
  • s 的每一个可能值的概率等于:【这个值出现的次数】 / 【6的 n 次方】。之所以可以这么计算,是因为所有事件都是等可能事件。所以可以先求出【这个值出现的次数】,再除 【6的 n 次方】。

求各个点数和出现的次数:-----动态规划
1、定义状态dp[n][s]:
dp[n][s] 表示投掷 n 个骰子,n 个朝上的面的点数之和为 s 的事件出现的次数。

2、状态转移方程
dp[n][s] += dp[n - 1][s - k],k 属于 [1, 6],(s - k)>=(n-1)

  • 注:(s - k)>=(n-1),因为s-k代表,n-1个骰子的点数和;又因n-1个骰子的点数和的最小值n-1,则s-k不能小于n-1.
  • 之所以dp[n][s]可以用比他少一个骰子的dp[n - 1][s - k]表示,是因为相当于把n个骰子分成n-1个1个两份,而一个骰子不管哪个点数朝上的次数都是1,所以n个骰子朝上的点数和取决于n-1个骰子的情况。
  • 取个例子:
    在这里插入图片描述

3、初始状态:
dp[1][k]=1,k 属于 [1, 6]。即,一个骰子哪个点数朝上的次数都为1.

4、返回值:
dp[n][s],s属于[n,6*n]

  • 分析返回值是有必要的,因为通过返回值,我们发现,希望dp[n][s]中的n能代表n个骰子的情况,这就需要刚开始初始化dp数组时多开一个空间。

代码:

class Solution {
public:
    vector<double> dicesProbability(int n) {
        //初始化时多开一个空间(n+1,6*n+1),是为了让dp[i][j],i=x,就代表x个骰子。
        //若不多开定义(n,6*n),则dp[0][j]代表1个骰子,这样下标混乱,容易出错。
        vector<vector<int>> dp(n+1, vector<int>(6*n+1,0));
        vector<double> res;
        //初始化
        for(int s=1; s<=6; s++){
            dp[1][s]=1;
        }
        for(int i=2; i<=n; i++){
            for(int j=i; j<=6*i; j++){
                for(int k=1; k<=6; k++){
                    //j-k代表,i-1个骰子的点数和;又因i-1个骰子的点数和的最小值i-1,则j-k不能小于i-1.
                    if((j-k)<(i-1)) break;
                    dp[i][j]+=dp[i-1][j-k];
                }
            }
        }
        double all=pow(6.0,n);
        for(int i=n; i<=6*n; i++){
            res.push_back(dp[n][i]/all);
        }
        return res;
    }
};

注意:
定义:vector res ,添加元素用 res.push_back();
定义:vector res(n), 添加元素用 res[i]=3;
若用反,则出错。

面试题63. 股票的最大利润

假设把某股票的价格按照时间先后顺序存储在数组中,请问买卖该股票一次可能获得的最大利润是多少?

示例 1:

输入: [7,1,5,3,6,4]
输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
     注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。
示例 2:

输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0

题解:
模式识别:最大利润—>最优解---->动态规划

在这里插入图片描述
代码:

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        if(prices.size() <= 1) return 0;
        vector<int> dp(prices.size());
        dp[0]=0;
        int minVal=prices[0];
        for(int i=1; i<prices.size(); i++){
            dp[i]=max(dp[i-1] , prices[i]-minVal);
            minVal = min(prices[i] , minVal);
        }
        return dp[prices.size()-1];
    }
};

时间复杂度:O(N)
空间复杂度:O(N)

由于dp[i]只和dp[i-1]、prices[i]-minVal有关,因此可使用一个变量maxProfit滚动记录,代替 dp 列表

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        if(prices.size() <= 1) return 0;
        int maxProfit=0;//maxDiff初始值为dp[0];
        int minVal=prices[0];
        for(int i=1; i<prices.size(); i++){
            maxProfit = max(maxProfit , prices[i]-minVal);
            minVal = min(prices[i] , minVal);
        }
        return maxProfit;
    }
};

时间复杂度:O(N)
空间复杂度:O(1)

知识点:

1、动态规划相对于递归是空间换时间,时间效率高了,但存储消耗大了。
时间效率高是因为,相比于递归的从上向下计算,其从下向上计算避免了很多重复计算;
存储量大是因为,动态规划需要创建一个一维或二维的数组来记录计算过程中的状态。

2、substr有2种用法:

假设:string s = "0123456789";

string sub1 = s.substr(5); //只有一个数字5表示从下标为5开始一直到结尾:sub1 = "56789"

string sub2 = s.substr(5, 3); //从下标为5开始截取长度为3位:sub2 = "567"
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值