利用历史数据记录推导新问题的解
经典问题:青蛙爬楼梯、斐波那契数列、01背包问题、最大序列和、路径问题等
解题思想:确定状态(最后一步:选择or不选择,子问题划分**),转移方程(dp[i][j] 与 dp[i-1][j-1]…的关系),开始和边界条件
题型一
最大子序列和 - 力扣53.
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
int maxSubArray(vector<int>& nums) {
int n=nums.size();
int maxsum=nums[0];
if(n==1)
return nums[0];
vector<int> dp(n);
dp[0]=nums[0];
//考虑边界条件,数组只有1个
for(int i=1;i<n;i++){
dp[i]=max(nums[i],dp[i-1]+nums[i]);
maxsum=max(dp[i],maxsum);
}
return maxsum;
}
股票买卖问题 - 力扣121
给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0
int maxProfit(vector<int>& prices) {
int n=prices.size();
if(n==0)
return 0;
int minprice=prices[0];
vector<int> dp;
dp.resize(n);
dp[0]=0;
for(int i=1;i<n;i++){
dp[i]=max(dp[i-1],prices[i]-minprice);
minprice=min(prices[i],minprice);
}
return dp[prices.size()-1];
}
二维dp,确定状态(选择or不选择)
多次买卖股票最多存有一只股票在手 - 力扣122
给定一个数组 prices ,其中 prices[i] 是一支给定股票第 i 天的价格。设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)
int maxProfit(vector<int>& prices) {
int n=prices.size();
//dp算法,第i天可能两种情况,没有股票在手dp[i][0],有一只股票在手dp[i][1]
//dp[i][0]=max(dp[i-1][0],dp[i-1][1]+prices[i]);
//dp[i][1]=max(dp[i-1][1],dp[i-1][0]-prices[i]);
vector<vector<int>> dp(n,vector<int>(2));
dp[0][0]=0;
dp[0][1]=-prices[0];
for(int i=1;i<n;i++){
dp[i][0]=max(dp[i-1][0],dp[i-1][1]+prices[i]);
dp[i][1]=max(dp[i-1][1],dp[i-1][0]-prices[i]);
}
return dp[n-1][0];
}
多维dp - 力扣123
给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。设计一个算法来计算你所能获取的最大利润。你最多可以完成两笔交易。注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)
三维数组
int maxProfit(vector<int>& prices) {
if(prices.size()==0) return 0;
//dp[i][j] 第i天 的 j 操作
//j=0 没有操作
//j=1 第一次买入
//j=2 第一次卖出
//j=3 第二次买入
//j=4 第二次卖出
//当天操作的最大值可能是 i-1 的记录,或者 i 天操作
vector<vector<int>> dp(prices.size(),vector<int>(5,0));
dp[0][1]=-prices[0];
dp[0][3]=-prices[0];
for(int i=1;i<prices.size();i++){
dp[i][0]=dp[i-1][0];
dp[i][1]=max(dp[i-1][1],dp[i-1][0]-prices[i]);
dp[i][2]=max(dp[i-1][2],dp[i-1][1]+prices[i]);
dp[i][3]=max(dp[i-1][3],dp[i-1][2]-prices[i]);
dp[i][4]=max(dp[i-1][4],dp[i-1][3]+prices[i]);
}
return dp[prices.size()-1][4];
}
int maxProfit(vector<int>& prices) {
//dp[天数][是否持股][卖出次数]
if(prices.size()<=1) return 0;
int dp[prices.size()][2][3];
dp[0][0][0]=0;//第一天没有买入卖出
dp[0][0][1]=dp[0][1][1]=INT_MIN/2;//防止后面加减越界,选择一个极小值的的一半,不影响结果
dp[0][0][2]=dp[0][1][2]=INT_MIN/2;//防止后面加减越界,选择一个极小值的的一半,不影响结果
dp[0][1][0]=-prices[0];
for(int i=1;i<prices.size();i++){
dp[i][0][0]=0;
dp[i][0][1]=max(dp[i-1][1][0]+prices[i],dp[i-1][0][1]);
dp[i][0][2]=max(dp[i-1][1][1]+prices[i],dp[i-1][0][2]);
dp[i][1][0]=max(dp[i-1][0][0]-prices[i],dp[i-1][1][0]);
dp[i][1][1]=max(dp[i-1][0][1]-prices[i],dp[i-1][1][1]);
dp[i][1][2]=INT_MIN;
}
return max(0,max(dp[prices.size()-1][0][1],dp[prices.size()-1][0][2]));
}
最多交易k次
int maxProfit(int k, vector<int>& prices) {
//n天最多交易n/2次,所以k最大为n/2
//dp0 一直没买
//dp1 买了1次
//dp2 买1卖1
//dp3 买2卖1
//dp4 买2卖2
//...
//dp2k 买k卖k
int n=prices.size();
if(n<=1||k==0) return 0;
k=min(n/2,k);
vector<int> dp(2*k+1,INT_MIN/2);
dp[0]=0;
dp[1]=-prices[0];
for(int i=1;i<n;i++){
for(int j=1;j<2*k+1;j++){
if(j&1==1){
dp[j]=max(dp[j],dp[j-1]-prices[i]);
}
else{
dp[j]=max(dp[j],dp[j-1]+prices[i]);
}
}
}
return dp[2*k];
}
题型二
不同路径总数 - 力扣62
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。问总共有多少条不同的路径?
int uniquePaths(int m, int n) {
vector<vector<int>> f(m, vector<int>(n));
for (int i = 0; i < m; ++i) {
f[i][0] = 1;
}
for (int j = 0; j < n; ++j) {
f[0][j] = 1;
}
for (int i = 1; i < m; ++i) {
for (int j = 1; j < n; ++j) {
f[i][j] = f[i-1][j] + f[i][j-1];
}
}
return f[m-1][n-1];
}
最小路径和 - 力扣64
给定一个包含非负整数的 m x n 网格 grid ,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。每次只能向下或者向右移动一步。
int minPathSum(vector<vector<int>>& grid) {
if (grid.size()==0 || grid[0].size() == 0) {
return 0;
}
int rows = grid.size(), columns = grid[0].size();
auto dp = vector<vector<int>> (rows, vector<int>(columns));
dp[0][0] = grid[0][0];
for (int i = 1; i < rows; i++) {
dp[i][0] = dp[i-1][0] + grid[i][0];
}
for (int j = 1; j < columns; j++) {
dp[0][j] = dp[0][j-1] + grid[0][j];
}
for (int i = 1; i < rows; i++) {
for (int j = 1; j < columns; j++) {
dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + grid[i][j];
}
}
return dp[rows-1][columns-1];
}
解码方法 - 力扣91
int numDecodings(string s) {
int n=s.size();
vector<int> dp(n+1);
dp[0]=1;
for(int i=1;i<=n;i++){
//当前字符不是0,即可以解码为一个字母,解码方案与前一个相同dp[i]=dp[i-1]
if(s[i-1]!='0')
dp[i]=dp[i]+dp[i-1];
//当前字符与前一个字符(非0)可以组成小于等于26的数字,解码方案可以增加dp[i]=dp[i-1]+dp[i-2]
if(i>1&&s[i-2]!='0'&&((s[i-2]-'0')*10+(s[i-1]-'0')<=26))
dp[i]=dp[i]+dp[i-2];
}
return dp[n];
}
题型三
编辑距离 - 力扣72
给你两个单词 word1 和 word2,请你计算出将 word1 转换成 word2 所使用的最少操作数 。你可以对一个单词进行如下三种操作:
1插入一个字符 dp[i][j] = dp[i][j-1]+1
2删除一个字符 dp[i][j] = dp[i-1][j]+1
3替换一个字符 dp[i][j] = dp[i-1][j-1]+1
int minDistance(string word1, string word2) {
int m=word1.size(),n=word2.size();
vector<vector<int>> dp(m+1,vector<int>(n+1));
dp[0][0]=0;
for(int i=1;i<=n;i++){
dp[0][i]=dp[0][i-1]+1;
}
for(int i=1;i<=m;i++){
dp[i][0]=dp[i-1][0]+1;
}
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
if(word1[i-1]==word2[j-1]) dp[i][j]=dp[i-1][j-1];
else dp[i][j]=min(dp[i-1][j-1],min(dp[i-1][j],dp[i][j-1]))+1;
}
}
return dp[m][n];
}