动态规划
把数字翻译成字符串
题目描述
给定一个数字,我们按照如下规则把它翻译为字符串: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时,23的矩形块有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];
}
};