剑指Offer刷题-动态规划

动态规划

把数字翻译成字符串

题目描述

给定一个数字,我们按照如下规则把它翻译为字符串:0 翻译成 “a” ,1 翻译成 “b”,……,11 翻译成 “l”,……,25 翻译成 “z”。一个数字可能有多个翻译。请编程实现一个函数,用来计算一个数字有多少种不同的翻译方法。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/ba-shu-zi-fan-yi-cheng-zi-fu-chuan-lcof

思路

dp[i]表示前i个数位翻译成字符串的方案数。
1)当digits[i] 和 digits[i-1]能被一起翻译成一个字符的时候:dp[i] = dp[i-1] + dp[i-2]
dp[i-1]表示只把digits[i] 翻译成对应的字符,dp[i-2]表示把digits[i] 和 digits[i-1]一起翻译成对应字符;
2)当digits[i] 和 digits[i-1]不能被一起翻译成一个字符的时候:dp[i] = dp[i-1]
【这里要注意 比如09不等价于9】比如9有对应的字符,09没有对应的字符,
因此,digits[i] *10+digits[i-1]要大于等于10,小于等于25时才能被一起翻译成一个字符。

#include<string>
class Solution {
public:
    int translateNum(int num) {
        /*
            动态规划
            dp[i]为前i个字符的翻译方案数
            则有     dp[i-2] + dp[i-1],  当 xi-1 xi 作为整体可被翻译时  00~09 都是不能整体翻译的
            dp[i] =
                     dp[i-1], 当 xi-1 xi 作为整体不可被翻译时  00~09 都是不能整体翻译的
        */
        if(num<=9)return 1;
        vector<int>dp;
        string numstr = to_string(num);
        dp.push_back(1);
        string hTwo = numstr.substr(0,2); // 从0开始 输出2个字符
        int h2 = stoi(hTwo);
        if(h2<=25)dp.push_back(2);
        else dp.push_back(1);
        for(int i=2;i<numstr.size();i++){
            hTwo = numstr.substr(i-1,2);
            h2 = stoi(hTwo);
            if(h2>=10&&h2<=25){ // 当前两位可以被整体翻译   506 这里要>=10 00~09 都是不能整体翻译的
                dp.push_back(dp[i-2]+dp[i-1]);
            }else { // 当前两位不可以被整体翻译
                dp.push_back(dp[i-1]);
            }
        }
        return dp[numstr.size()-1];
    }
};

股票的最大利润

题目描述

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

思路

dp[i]表示前i填交易最大能获得的利润;
用变量min维护前i天的最低价:
1)如果当天价格price[i-1]-min>dp[i-1],即当天价格prices[i-1]和历史最低价的差值大于历史最大利润dp[i-1],意味着在历史最低价的时候买入,当天卖出,能够获得比历史最大利润更大的利润dp[i] = prices[i-1]-min
【这里需要注意,dp里面为了方便计算事先放了一个0,所以prices[i-1]就表示第i天的价格】
2)如果天价格price[i-1]-min<=dp[i-1],即当天价格prices[i-1]和历史最低价的差值小于等于历史最大利润dp[i-1],则dp[i] = dp[i-1];
【注意在此过程中要不停的维护min】

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        /*
            动态规划
            dp[i] 表示 以prices[i]结尾的 最大利润
            以下为了方便计算 dp[i]表示以prices[i-1]结尾的最大利润 即截至第i天历史的最大利润
            dp[i] = max(dp[i-1], prices[i]-min)   min为前i日最低价格
        */
        vector<int>dp(1, 0);
        int min=99999999;
        int max=0;
        for(int i=1;i<=prices.size();i++){
            // 实时更新前i天的最低价格 prices[i-1] 表示第i天的价格
            // =这样可以实时计算在第i填卖出的最大利润=
            if(prices[i-1]<min)min = prices[i-1];

            // 如果以prices[i-2]结尾的最大利润dp[i-1]
            // (即截至第i-1天历史的最大利润)  >  第i天价格-历史最低价【即当天卖出的最大利润】
            if(dp[i-1]>(prices[i-1]-min)){
                dp.push_back(dp[i-1]);        // 截至第i天的最大利润 = 截至第i-1天历史的最大利润
                //= dp[i-1];  // 若以prices[i-1]结尾的最大利润dp[i-1]大于今天价格prices[i]-历史最低价min
            }
            else{
                dp.push_back(prices[i-1]-min);// 更新历史最大利润
            }
            if(dp[i]>max)max=dp[i];
        }
        return max;
    }
};

礼物的最大价值

题目描述

在一个 m*n 的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于 0)。你可以从棋盘的左上角开始拿格子里的礼物,并每次向右或者向下移动一格、直到到达棋盘的右下角。给定一个棋盘及其上面的礼物的价值,请计算你最多能拿到多少价值的礼物?
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/li-wu-de-zui-da-jie-zhi-lcof

思路

dp[i][j]表示到达i,j能够拿到的礼物的最大价值;
由于每次只能向右或者向下移动,因此,每格的最优解肯定来源于改格上面一格或者左面一格。
即 dp[i][j] = max{ dp[i][j-1]+array[i][j] , dp[i-1][j]+array[i][j]}
dp[i][j-1]+array[i][j] 表示从左边过来,捡起本格i,j上的礼物能够获得的最大价值;
dp[i-1][j]+array[i][j] 表示从上边过来,捡起本格i,j上的礼物能够获得的最大价值;
上述两者取较大者即可。

class Solution {
public:
    int maxValue(vector<vector<int>>& grid) {
        int dp[205][205];
        for(int i=0;i<=grid.size();i++){
            for(int j=0;j<=grid[0].size();j++)
            dp[i][j] = 0;
        }
        //dp[1][1] = grid[0][0];
        for(int i=1;i<=grid.size();i++){
            for(int j=1;j<=grid[0].size();j++){
                if(dp[i-1][j]>dp[i][j-1]){
                    dp[i][j] = dp[i-1][j] + grid[i-1][j-1];
                }else{
                    dp[i][j] = dp[i][j-1] + grid[i-1][j-1];
                }
            }
        }
        return dp[grid.size()][grid[0].size()];
    }
};

n个骰子的点数

题目描述

把n个骰子扔在地上,所有骰子朝上一面的点数之和为s。输入n,打印出s的所有可能的值出现的概率。
你需要用一个浮点数数组返回答案,其中第 i 个元素代表这 n 个骰子所能掷出的点数集合中第 i 小的那个的概率。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/nge-tou-zi-de-dian-shu-lcof

思路

dp[i][j]表示i颗骰子能掷出点数和为j的情况数量
dp[i][j] = dp[i-1][j-6] + dp[i-1][j-5] + dp[i-1][j-4] + dp[i-1][j-3] + dp[i-1][j-2] + dp[i-1][j-1];
1)dp[i-1][j-6]表示当前骰子掷出6,前i-1颗骰子掷出j-6的情况;
2)dp[i-1][j-5]表示当前骰子掷出5,前i-1颗骰子掷出j-5的情况;
3)dp[i-1][j-4]表示当前骰子掷出4,前i-1颗骰子掷出j-4的情况;
4)dp[i-1][j-3]表示当前骰子掷出3,前i-1颗骰子掷出j-3的情况;
4)dp[i-1][j-2]表示当前骰子掷出2,前i-1颗骰子掷出j-2的情况;
4)dp[i-1][j-1]表示当前骰子掷出1,前i-1颗骰子掷出j-1的情况;
以上需要注意j的范围,j的范围不合法的要扣掉对应的项
此外,i颗骰子最少掷出的点数是i,最多掷出的点数是6i

class Solution {
public:
    vector<double> twoSum(int n) {
        vector<double> res;
        int dp[15][70];
        int max[15];
        int total=1;
        for(int i=1;i<=n;i++){
            max[i]=6*i;
            total = total*6;
        }
        for(int i=0;i<=n;i++){
            for(int j=0;j<=max[n];j++){
                dp[i][j]=0;
            }
        }
        for(int i=1;i<=6;i++)dp[1][i]=1;
        
        for(int i=2;i<=n;i++){
            for(int j=i;j<=max[i];j++){
                if(j-6>=0){
                    dp[i][j] = dp[i-1][j-6] + dp[i-1][j-5] + dp[i-1][j-4] + dp[i-1][j-3] + dp[i-1][j-2] + dp[i-1][j-1];
                }
                else if(j-5>=0){
                    dp[i][j] = dp[i-1][j-5] + dp[i-1][j-4] + dp[i-1][j-3] + dp[i-1][j-2] + dp[i-1][j-1];
                }
                else if(j-4>=0){
                    dp[i][j] = dp[i-1][j-4] + dp[i-1][j-3] + dp[i-1][j-2] + dp[i-1][j-1];
                }
                else if(j-3>=0){
                    dp[i][j] = dp[i-1][j-3] + dp[i-1][j-2] + dp[i-1][j-1];
                }
                else if(j-2>=0){
                    dp[i][j] = dp[i-1][j-2] + dp[i-1][j-1];
                }
                else if(j-1>=0){
                    dp[i][j] = dp[i-1][j-1];
                }  
            }
        }
        for(int j=1;j<=max[n];j++){
            if(dp[n][j]!=0){
                //printf("%d颗骰子,和为%d的情况数有%d种\n",n,j,dp[n][j]);
                double p = double(dp[n][j])/(double)total;
                //printf("%f\n",p);
                p = (int)(p*100000 + 0.5)/100000.0;
                //printf("%f\n",p);
                res.push_back(p);  
            }
        }
        return res;
    }
};

丑数

题目描述

把只包含质因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含质因子7。 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。

思路

class Solution {
public:
    int GetUglyNumber_Solution(int index) {
        // 第一步.定义第一个丑数是1
        vector<int>dp(1,1); 
        // 第二步.初始化丑因子 2 3 5一开始所使用的丑基数
        // [所有丑数都可以作为丑基数], 丑基数乘上一个丑因子2/3/5就可以产生一个新的丑数
        int a=0; // 丑数2所使用的丑基数的下标 一开始就是dp[0]=1; 
        int b=0; // 丑数3所使用的丑基数的下标 一开始也是dp[0]=1; 
        int c=0; // 丑数5所使用的丑基数的下标 一开始也是dp[0]=1;
        // dp[i-1]表示第i个丑数
        for(int i=1;i<index;i++){
            // 第三步.2 3 5 根据当前丑基数生成新的丑数 
            // 选择最小的新生丑数 作为新增丑数
            int min=dp[a]*2;
            if(dp[a]*2>dp[b]*3){
                min = dp[b]*3;
            }
            if(min>dp[c]*5){
                min = dp[c]*5;
            }
            dp.push_back(min);
            // 第四步.更新 丑因子2/3/5分别使用的丑基数 
            // 注意:一定得是分别if而不是if else if 这样重复的情况才会被过滤
            if(dp[a]*2==min){
                a++;
            }
            if(dp[b]*3==min){
                b++;
            }
            if(dp[c]*5==min){
                c++;
            }
        }
        // 最后返回第n个丑数
        return dp[index-1];
    }
};

连续子数组的最大和

题目描述

HZ偶尔会拿些专业问题来忽悠那些非计算机专业的同学。今天测试组开完会后,他又发话了:在古老的一维模式识别中,常常需要计算连续子向量的最大和,当向量全为正数的时候,问题很好解决。但是,如果向量中包含负数,是否应该包含某个负数,并期望旁边的正数会弥补它呢?例如:{6,-3,-2,7,-15,1,2,2},连续子向量的最大和为8(从第0个开始,到第3个为止)。给一个数组,返回它的最大连续子序列的和,你会不会被他忽悠住?(子向量的长度至少是1)

思路

dp[i]表示前i个元素能产生的最大子数组和,则dp[i+1] = max{dp[i]+array[i], array[i]}
即当dp[i]>0时,才对dp[i+1]有贡献,否则dp[i]就等于array[i]本身。

class Solution {
public:
    int FindGreatestSumOfSubArray2(vector<int> array){
        int maxSubSeqSum = array[0];
        int tmp=0;
        
        for(int i=0;i<array.size();i++){
            if(tmp+array[i]<0){
                tmp=0;
            }else{
                tmp+=array[i];
            }
            maxSubSeqSum = max(tmp, maxSubSeqSum);
        }
        if(tmp!=0){
            return maxSubSeqSum;
        }
        return *max_element(array.begin(),array.end());
    }
    int FindGreatestSumOfSubArray(vector<int> array) {
        // 动态规划: dp[i]表示以array[i]结尾的最大连续子序列和
        vector<int> dp(array.size()+1,1);
        int maxSubSeqSum = dp[0] = array[0];
        for(int i=1;i<array.size();i++){
            dp[i] = max( array[i] , dp[i-1]+array[i] );
            // 这里体现最大 不管dp[i-1]+array[i]中array[i]对dp[i-1]的削弱,大的dp[i-1]已经被记录
            maxSubSeqSum = max(maxSubSeqSum,dp[i]);
        }
        //return maxSubSeqSum;
        return FindGreatestSumOfSubArray2(array);
    }
};

青蛙跳台阶II

题目描述

一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。

思路

设跳上第i级台阶的方法数为f[i]
【结论1】则f[i] = f[i-1]+ f[i-2] + … + f[0]
f[i-1] 从i-1级跨1步到i级台阶
f[i-2] 从i-2级跨2步到i级台阶

f[0] 从0级跨i步到i级台阶
【结论2】又有f[i-1] = f[i-2]+…+f[0], 所以有 f[i] = 2*f[i-1]
f[1] = 1;
f[2] = 2;
f[3] = 4;
f[4] = 8;
f[i] = 2的i-1次方 其中i>=1;
【边界条件】f[0] = 1;

class Solution {
public:
    int jumpFloorII(int number) {
        if(number==0||number==1) return 1;
        return pow(2,number-1); 
    }
};

矩形覆盖

题目描述

我们可以用21的小矩形横着或者竖着去覆盖更大的矩形。请问用n个21的小矩形无重叠地覆盖一个2n的大矩形,总共有多少种方法?
比如n=3时,2
3的矩形块有3种覆盖方法:
在这里插入图片描述

思路

跟青蛙跳台阶1是一样的,一次性可以跳1级或者2级台阶。

class Solution {
public:
    int rectCover(int number) {
        vector<int>dp(number+1,0);
        dp[0] = 0;
        dp[1] = 1;
        dp[2] = 2;
        for(int i=3;i<=number;i++){
            dp[i] = dp[i-1] + dp[i-2];
        }
        return dp[number];
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值