动态规划
适用范围:
- 动态规划DP,在查找有多个多重叠子问题情况的最优解时有效。
- DP只用于有最优子结构的问题(最优子结构:局部最优解完全能决定全局最优解),问题能够分解成子问题。
动态规划的特点:
- 它将问题重新组合成子问题,为避免多次计算,保存每一次的结果。
- 重点是找到状态转移方程
动态规划 vs 其他遍历算法
同:都是将问题拆解成子问题,然后求解。
异:
1)动态规划保留了子问题的解,避免重复计算。可以看作是带有记录状态的优先搜索。
2)动态规划是自下而上的。
3)带有记录的优先搜索是自上而下的,先解决从父问题都锁子问题。
4)如果题目需只需要最终状态,使用动态搜索方便;如果题目需要所有的路径,使用带有状态记录的优先搜索方便。
基本动态规划:一维
70、爬楼梯
给了一个楼梯总数n,每次只能爬一步或者两步,求爬这个n阶的楼梯一共有多少种可能性。
解答:
这个问题可以拆解成子问题,如果说已经知道了爬0-n-1中每一个高度阶梯有多少种方法,再求n阶楼梯有多少种可能方法。此时可以知道到第n阶阶梯的最后一步只能爬1/2步骤,因此dp[n] = dp[n-1]+dp[n-2]
class Solution {
public:
int climbStairs(int n) {
//使用动态规划解决 dp数组表示楼梯一共n阶时,有多少种方法可以爬到楼顶
int size = max(n,2);
int* dp = new int[size+1];
dp[0] = 0;
dp[1] = 1;
dp[2] = 2;
for(int i = 3;i<=n;i++){
dp[i] = dp[i-1]+dp[i-2];
}
return dp[n];
}
};
需要注意边界条件
优化:注意到其实dp[i]只与前两个元素有关,因此可以只用两个变量来存储。
class Solution {
public:
int climbStairs(int n) {
//使用动态规划解决 dp数组表示楼梯一共n阶时,有多少种方法可以爬到楼顶
if(n<=2)return n;
int pre1 = 1,pre2 = 2,cur;
for(int i =3;i<= n;i++){
cur = pre1+pre2;
pre1 = pre2;
pre2 = cur;
}
return cur;
}
};
198、打家劫舍
给了一个nums数组,表示一条街上的每个房子的钱财数量,求一个劫匪在不同时打劫相邻两家的情况下,共能收获多少钱。
解答:使用dp数组,dp[i]表示当一共有i家店铺时,劫匪能打劫的最多钱财的数量。
那么dp[i] = max(dp[i-1],dp[i-2]+nums[i])//分为当前这房子打劫和不打劫两种情况,取其中的最大值。
class Solution {
public:
int rob(vector<int>& nums) {
//dp[i]表示在只有0~i的位置的房子时,一共能偷到的最大值
//特判
if(nums.size()==0)return 0;
if(nums.size()==1)return nums[0];
if(nums.size()==2)return max(nums[0],nums[1]);
vector<int> dp(nums.size(),0);
dp[0] = nums[0];
dp[1] = max(nums[0],nums[1]);
for(int i = 2;i<nums.size();i++){
dp[i] = max(dp[i-1],dp[i-2]+nums[i]);
}
return dp[nums.size()-1];
}
};
同样可以使用空间压缩
413、等差数列拆分
给了一个数组,从它中拆出一组连续的值,是它的连续子数组,求共有多少个连续等差子数组。
解法:动态规划,dp表示以i为结尾的子数组中,连续等差子数组的个数。
注意到如果在dp【i】中,则num【i】与其前面连续的三个数能构成一个等差数组,这个数组的差已经是固定的了,如果num【i+1】与num【i】、num【i-1】能够成等差数组,则dp【i】中的所有子串+num【i+1】都可以构成等差数组,并且num【i+1】、num【i】、num【i-1】三个构成了一个新的等差数组。
因此dp【i】 = dp【i-1】+1;
class Solution {
public:
int numberOfArithmeticSlices(vector<int>& nums) {
//注意:子数组是数组中的一个连续序列
//dp[i]表示当只有元素0-n-1时,以i为结尾的等差数组个数。
if(nums.size()<2)return 0;
vector<int> dp(nums.size(),0);
getans(dp,nums);
return sum_dp(dp);
}
void getans(vector<int>& dp,vector<int>& nums){
for(int i =2;i<nums.size();i++){
if(nums[i]-nums[i-1]==nums[i-1]-nums[i-2]){
dp[i] = dp[i-1]+1;
}
}
}
int sum_dp(vector<int>& dp){
int sum = 0;
for(int i = 0;i<dp.size();i++){
sum = sum+dp[i];
}
return sum;
}
};
基本动态规划:二维
64、最小路径和
给了一个二维数组,求从左上角到右下角的一条路径上的数字的总和的最小值。
解法:使用二维dp数组,其中dp[i][j]表示从(0,0)到(i,j)的路径上值之和的最小值。
注意:没有要求具体的路径,因此可以考虑dp,考虑到当前面的点的dp值都知道时,求dp[i][j]只有可能从(i-1,j)或者(i,j-1)位置来。(因为每次只能向右或者向下走)
class Solution {
public:
int minPathSum(vector<vector<int>>& grid) {
int m = grid.size();
int n = grid[0].size();
if(m==0||n==0)return 0;
vector<vector<int>> dp(m,vector<int>(n,0));
for(int i = 0;i<m;i++){
for(int j = 0;j<n;j++){
if(i==0&&j==0)dp[i][j] = grid[i][j];
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] = min(dp[i-1][j],dp[i][j-1])+grid[i][j];
}
}
}
return dp[m-1][n-1];
}
};
优化:可以对空间优化,成为一维的
221、最大的正方形
给了一个由字符0、1组成的二维矩阵,求其中最大的全由1组成的正方形的面积大大小。
解法:注意到其中没有要求返回具体的东西,只是要返回一个结果,因此考虑使用动态规划实现。
设dp数组表示以ij为右下角的正方形的最大大小。去除了边界条件后,注意到某个位置的dp不仅和当前位置的0/1值有关,如果为0,则当前位置的dp为0,如果为1,则dp[i][j] = min(dp[i-1][j-1],dp[i-1][j],dp[i][j-1])+1。
class Solution {
public:
int maximalSquare(vector<vector<char>>& matrix) {
//使用动态规划,dp[i][j]表示以ij为右下角的长方形的最大值边长
int m = matrix.size();
int n = matrix[0].size();
int ret=0;
vector<vector<int>> dp(m,vector<int>(n,0));
for(int i = 0;i<m;i++){
for(int j =0;j<n;j++){
// cout<<"i="<<i<<",j="<<j<<endl;
if(i==0||j==0){
// cout<<"i==0||j==0"<<endl;
dp[i][j] = matrix[i][j]-'0';
// cout<<"dp["<<i<<"]["<<j<<"]= "<<dp[i][j]<<endl;
ret = max(ret,dp[i][j]);
// cout<<"ret = "<<ret<<endl;
}
else{
if(matrix[i][j]=='1'){
dp[i][j] = 1+ min(dp[i-1][j-1],min(dp[i-1][j],dp[i][j-1]));
// cout<<"dp["<<i<<"]["<<j<<"]= "<<dp[i][j]<<endl;
ret = max(ret,dp[i][j]);
// cout<<"ret = "<<ret<<endl;
else{
dp[i][j]=0;
}
}
}
}
return ret*ret;
}
};
注意:踩坑1、输入为char类型,2、返回值为面积
分割类问题
279、最优平方数
给一个正整数n,求他最少能用几个完全平方数的和组成。
解答:注意到没有要求求它的过程,并且发现当问题规模为0-n-1都知道时,dp[n] = min{dp[n-ii]+1}(ii<=n}。因此使用动态规划。
91、解码方式
给了以下字母和字符串的映射方式
‘A’ -> 1
‘B’ -> 2
…
‘Z’ -> 26
给了一个数字字符串,求他能被用多少种字母串映射而成。
注意:07!=7,没有字母映射到的以0开头的。
解答:注意到只求个数,没有要求具体的解答。
当求dp[n]时,如果dp[0-n-1]都知道了,那么可以根据dp和s[n-1]和s[n]求出dp[n]
dp[n]表示在下标为0~n-1的字符串时,有多少种可能的方法。
要根据当前的字符和前一个字符的值来判断dp[n]=?
1、如果前一个值为0||>=3则后一个一定不能和前一个的值拼接。
1)如果此时值为0,则错误。
2、如果前一个值为1||2则要根据后一个点值判断
1)如果后一个值为0,那么必须和前一个拼接,dp[n]=dp[n-2]
2)如果pre==2&&后一个大于7,则一定不能和前一个拼接dp[n] = dp[n-1]
3)其他,i可以和前一个拼接,也可以不拼接dp[n]=dp[n-1]+dp[n-2]。
class Solution {
public:
int numDecodings(string s) {
//dp[i]表示在只有1-i位置的字符时,有多少种编码的可能
int n =s.size();
vector<int> dp(n+1,0);
//特判
//
int pre = s[0]-'0';
int cur;
if(pre==0)return 0;
if(n==0)return 0;
if(n==1)return 1;
dp[0] =1;
dp[1] =1;
for(int i =2;i<=n;i++){
cur = s[i-1]-'0';
//按照情况判断
if(pre==0||pre>=3){
if(cur==0)return 0;
dp[i] = dp[i-1];
}else{
//pre == 1||2
if(cur == 0)dp[i]=dp[i-2];
else if(pre==2&&cur>=7)dp[i]=dp[i-1];
else{
dp[i] = dp[i-1]+dp[i-2];
}
}
pre = cur;
}
return dp[n];
}
};
注意:dp[0]应该初始化为1,表示前面的分割是唯一的。
子序列问题
300、最长递增子序列
给了一个数组,求这个数组中最长的递增子序列的长度。
子序列指的是从数组中按从前往后的顺序抽出的一组数,他们可以不相连。
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
int n = nums.size();
vector<int> dp(n,1);
int max_int = 0;
//特判
if(n==1)return 1;
for(int i = 1;i<n;i++){
int tmp = 1;
for(int j = i-1;j>=0;j--){
if(nums[i]>nums[j]){
tmp = max(dp[j]+1,tmp);
}
}
dp[i] = tmp;
max_int = max(dp[i],max_int);
}
return max_int;
}
};
踩坑:注意每一个dp要找到前面一个数字比它小的最大的dp,而不是找到一个就停止了。
1143、最长公共子序列
给了两个字符串,求这两个字符串中最长的公共子序列的长度为多少。
解答:可以使用动态规划,二维动态规划,dp[I][J]表示在S1的到i位置,和S2到j位置两个串的最大公共子序列长度。
如果说S1【i-1】==S2【j-1】则这两个位置的值相等,要比两个串都少这个位置的值+1 dp[i][j]=dp[i-1][j-1]+1。
如果说两个位置的值不相等,则要选取dp[i-1][j]和dp[i][j-1]中更大的那个。因为dp表示的是两个串到当前位置的子串的最大公共子序列的长度。
class Solution {
public:
int longestCommonSubsequence(string text1, string text2) {
int m = text1.length();
int n = text2.length();
vector<vector<int>> dp((m+1),vector<int>(n+1,0));
// vector<vector<int>> dp(m + 1, vector<int>(n + 1, 0));
for(int i = 1; i<=m ;i++){
for(int j = 1;j<=n;j++){
if(text1[i-1]==text2[j-1]){
dp[i][j] = dp[i-1][j-1]+1;
}else{
dp[i][j] = max(dp[i-1][j],dp[i][j-1]);
}
}
}
return dp[m][n];
}
};
背包问题
是一种组合优化的NP问题:有N个物品和容量为W的背包,每个物品有自己的体积w价值v,求拿哪些东西可以使得最大化所拿东西的价值。
0-1背包问题:每种东西只能拿0/1个。
完全背包问题/无界背包问题:每种东西能拿无限次数个。
注意到:可以使用二维动态规划来解决这个问题,其中dp[i][j]表示在前i个物品,重量不超过j情况下所获得的最大的价值。那么对于dp[i][j]如果不拿第i个物品,则dp[i][j] = dp[i-1][j];如果拿了第i个物品,则说明会占用一定的空间,dp[i][j] = dp[i][j-v[i-1];
0-1背包问题
int knapsack(vector<int> weights, vector<int> values, int N, int W){
vector<vector<int>> dp(N+1,vector<int>(W+1,0));
for(int i =1; i<=N; i++)
{
int value = values[i-1];
int weight = weights[i-1];
for(int j = 1; j<=M; j++){
//判断当前的东西能否被放进去
if(weight <= j)
{
dp[i][j] = max(dp[i-1][j],dp[i-1][j-weight]+value);
}else{
dp[i][j] = dp[i-1][j];
}
}
return dp[N][W];
}
完全背包问题:
dp[i][j] = max(dp[i-1][j],dp[i-1][j-w]+v,dp[i-1][j-2w]+2v,…);
但是考虑到dp[I][J-W]已经考虑过了dp[i][j-w] = max(dp[i-1][j-w]+v,dp[i-1][j-2w]+2v,…)
因此dp[i][j] = max(dp[i-1][j],dp[i][j-w]+v);
int knapsack(vector<int> weights, vector<int> values, int N, int W){
vector<vector<int>> dp(N+1,vector<int>(W+1,0));
for(int i =1; i<=N; i++)
{
int value = values[i-1];
int weight = weights[i-1];
for(int j = 1; j<=M; j++){
//判断当前的东西能否被放进去
if(weight <= j)
{
dp[i][j] = max(dp[i-1][j],dp[i][j-weight]+value);
}else{
dp[i][j] = dp[i-1][j];
}
}
return dp[N][W];
}
空间压缩:
如果您实在不想仔细思考,这里有个简单的口诀:0-1 背包对物品的迭代放在外层,里层的
体积或价值逆向遍历;完全背包对物品的迭代放在里层,外层的体积或价值正向遍历。
416、分割等和子集
给了一个数组,求是否能等分为两个和一样的子数组。
先求和,然后使用dp找能否和为其一半的值。
使用二维dp,因为考虑有两个可以退的变量,一个是nums数组的个数,另一个是和。
class Solution {
public:
bool canPartition(vector<int>& nums) {
//先求和,然后使用dp找能否和为其一半的值。
//使用二维dp,因为考虑有两个可以退的变量,一个是nums数组的个数,另一个是和。
int m = nums.size();
int sum = 0;
for(int num: nums){
sum += num;
}
if(sum%2==1)return false;
sum = sum/2;
//dp表示在有到数到第i个数时,和为j时,数组中是否有数的和为1/2sum
vector<vector<bool>> dp(m+1,vector<bool>(sum+1,false));
for(int i = 0;i<=m;i++){
dp[i][0] = true;
}
for(int i =1;i<=m;i++){
int num = nums[i-1];
for(int j = 1;j<=sum;j++){
if(num>sum)return false;
else{
if(j-num>=0)
dp[i][j] = dp[i-1][j-num] || dp[i-1][j];
else{
dp[i][j] = dp[i-1][j];
}
}
}
}
return dp[m][sum];
}
};
踩坑,首先初始化的时候要注意,dp[0][j]和dp[i][0]的值,以及其他所有的初始化,一般设置为false。
注意到j=0时,表示和为0,则一定是true。
注意到i=0&& j!=0时,表示的是 没有任何一个数,但和不为0,一定为false。
注意到当i!=0时,如果说num<j,此时dp不一定是false,可能由前面的几个拥有的值支撑起来。直接dp[i-1][j]即可。
474、一和零
给了一个01字符串的数组,给出两个整数m和n。
求这个数组的一个子数组,其中的所有0的个数为m,1的个数为n,求这个字数组的最大个数。
把0和1看作两个背包。把每个字符串看作一个物品。因此可以看成是背包问题。
class Solution {
public:
int findMaxForm(vector<string>& strs, int m, int n) {
int len = strs.size();
vector<vector<vector<int>>> dp(len+1,vector(m+1,vector(n+1,0)));
for(int i = 1;i<=len;i++){
// cout<<"i="<<i<<endl;
auto [count_0,count_1] = count(strs[i-1]);
for(int j =0;j<=m;j++){
// cout<<"j="<<j<<endl;
for(int k = 0;k<=n;k++){
// cout<<"k="<<k<<endl;
dp[i][j][k] = dp[i-1][j][k];
// cout<<"dp = "<<dp[i][j][k]<<endl;
if((j-count_0)>=0&&(k-count_1)>=0){
// cout<<"if满足"<<endl;
dp[i][j][k] = max(dp[i-1][j][k],dp[i-1][j-count_0][k-count_1]+1);
// cout<<"dp = "<<dp[i][j][k]<<endl;
}
}
}
}
return dp[len][m][n];
}
pair<int,int> count(string str){
int count_0 = 0;
int count_1 = 0;
for(char s:str){
if(s=='0')count_0++;
if(s=='1')count_1++;
}
return make_pair(count_0,count_1);
}
};
注意:问题中出现了三个维度,因此使用了三维度数组,并且使用了pair和auto的组合。
322、零钱兑换
给了一个零钱数组,里面显示了各个硬币的价格,然后再给了一个amount。求最少能用多少个硬币来合成这个amount。
解题:可以使用dp,其中amount是背包,各个硬币为物品,每个硬币的value均为1,因此可以看作是完全背包问题。
class Solution {
public:
int coinChange(vector<int>& coins, int amount) {
//把amount看作背包,把每个价格的硬币看作货物,每个硬币的value均为1,因此可以看作是完全背包问题。
int n = coins.size();
vector<vector<int>> dp(n+1,vector<int>(amount+1,0));
for(int j = 1;j<=amount;j++)dp[0][j] = -1;
for(int i = 1;i<=n;i++){
int coin_number = coins[i-1];
for(int j =1;j<=amount;j++){
if(dp[i-1][j]==-1){
if(j-coin_number>=0&&dp[i][j-coin_number]!=-1){
dp[i][j] = dp[i][j-coin_number]+1;
}else{
dp[i][j] = -1;
}
}else{
dp[i][j] = dp[i-1][j];
if(j-coin_number>=0&&dp[i][j-coin_number]!=-1){
dp[i][j] = min(dp[i-1][j],dp[i][j-coin_number]+1);
}
}
}
}
return dp[n][amount];
}
};
字符串编辑
650、只有两个按键的键盘
给了一个字符,有一个键盘每次只能做两件事情,复制全部/粘贴,求给个n,想要获得长度为n的这个字母组成的串最少需要多少次。
class Solution {
public:
int minSteps(int n) {
//使用动态规划
vector<int> dp(n+1,0);
//dp表示扩张到i需要的次数
dp[1] = 1;
if(n<=1)return 0;
for(int i = 2;i<=n;i++){
// cout<<"i = "<<i<<endl;
dp[i]=i;
for(int j = 2;j<=sqrt(i);j++){
// cout<<"j = "<<j<<endl;
if(i%j==0){
// cout<<"i%j==0"<<endl;
dp[i] = dp[j]+dp[i/j];
// cout<<"dp = "<<dp[i]<<endl;
break;
}
}
}
return dp[n];
}
};
踩坑:注意到如果说不能被整除的数,只能从一开始翻倍,因此为他们赋予初值i。
10、
股票交易
股票交易类问题通常可以用动态规划来解决。对于稍微复杂一些的股票交易类问题,比如需
要冷却时间或者交易费用,则可以用通过动态规划实现的状态机来解决。
股票类问题:股票问题一共有六道:买卖股票的最佳时机(1(121),2(122),3(123),4(188))、含冷冻期、含手续费。本题是第一道,属于入门题目。
121、买卖股票最佳时机
给了一个股票每天价格的数组,求买入并在后面某天卖出的最大收益。
可以使用dp的方法,dp表示的是只有前i天时的最大收益
那么dp[i] = max(dp[i-1],price[i]-min_price)
并且需要一个参数一直记录最低的价格。
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n = prices.size();
if(n<=1)return 0;
int cur_min_buy = prices[0];
vector<int> dp(n,0);
for(int i = 1;i<n;i++){
int price = prices[i];
int earn = price - cur_min_buy;
dp[i] = max(earn,dp[i-1]);
cur_min_buy = min(price,cur_min_buy);
}
return dp[n-1];
}
};
可以进行空间的压缩,判断本天卖出是不是比之前的最大大。
122、买卖股票的最佳时机II
给了一个股票每日的价格,求多次买入卖出后的最大收益。
可以使用dp,注意股票可以多次买卖,因此可能在每一天都可能持有股票或者现金,并且如果某天想卖出,在他的前一天必须处于已经持有股票的状态,如果想要买入,前一天必须没有股票。
因此使用二维的dp表示当前持有的是股票还是现金
class Solution {
public:
int maxProfit(vector<int>& prices) {
//如果要在第i天买入,需要知道只有i-1天时的最大的卖出的收益。
//如果要在第i天卖出,需要知道只有i-1天时买入的最大的收益。
//因此需要二维数组
//dp[0]表示持有现金,dp[1]表示持有股票
int n = prices.size();
vector<vector<int>> dp(n,vector<int>(2,0));
dp[0][0] = 0;
dp[0][1] = -prices[0];
for(int i = 1;i<n;i++){
//想在今天持有现金的最大收益
// cout<<"i = "<<i<<endl;
dp[i][0] = max(dp[i-1][0],dp[i-1][1]+prices[i]);
// cout<<"dp[0]"<<dp[i][0]<<endl;
//想在今天买入的最大收益
dp[i][1] = max(dp[i-1][1],dp[i-1][0]-prices[i]);
// cout<<"dp[1]"<<dp[i][1]<<endl;
}
return dp[n-1][0];
}
};
踩坑:注意不要使用dp表示今天是买入还是卖出,使用其表示今天持有股票还是现金能体现的更清楚。
123、买卖股票的最佳时机III
给了一个数组,表示股票每日的价格,求最大的股票收益是多少,最多只能买卖两次。
使用三维的dp。
class Solution {
public:
int maxProfit(vector<int>& prices) {
//使用三维dp,由于每天结束时有三个状态:1、日期,2、持有现金/股票,3、股票的卖出次数
int n = prices.size();
//特判
if(n<=1)return 0;
//初始化
vector<vector<vector<int>>> dp(n,vector<vector<int>>(2,vector<int>(3,0)));
dp[0][0][0] = 0;
dp[0][1][0] = -prices[0];
dp[0][1][1] = -prices[0];
dp[0][1][2] = INT_MIN;
for(int i =1;i<n;i++){
//1、如果今日卖出过0次股票
//当前持有现金,说明从未进行股票交易
dp[i][0][0] = 0;
//如果当前持有股票,说明买入过一次
dp[i][1][0] = max(dp[i-1][1][0],dp[i-1][0][0]-prices[i]);
//2、如果进入卖出过一次股票
//如果当前持有现金
dp[i][0][1] = max(dp[i-1][0][1],dp[i-1][1][0]+prices[i]);
//如果持有股票
dp[i][1][1] = max(dp[i-1][1][1],dp[i-1][0][1]-prices[i]);
//3、如果买入卖出过两次股票
//如果当前持有现金
dp[i][0][2] = max(dp[i-1][0][2],dp[i-1][1][1]+prices[i]);
//如果持有股票
dp[i][1][2] = INT_MIN;
}
return max(dp[n-1][0][2],max(dp[n-1][0][1],dp[n-1][0][0]));
}
};
注意dp[0][][1/2];
188、邮票买卖IV
给了一个邮票价格数组和k,求在最多买卖k次的情况下能最大获益多少。
可以使用三维dp来实现。
class Solution {
public:
int maxProfit(int k, vector<int>& prices) {
//需要使用三维数组
int n = prices.size();
//特判
if(n<=1)return 0;
// k =min(k,n/2);
vector<vector<vector<int>>> dp(n,vector<vector<int>>(2,vector<int>(k+1,0)));
//初始化
for(int i = 0;i<k;i++){
dp[0][1][i] = -prices[0];
}
//dp[0][0][k] = 0;
dp[0][1][k]=INT_MIN;
//所有的dp[i][0][0]=0;
//所有的dp[i][1][0]=-price[i];
dp[0][1][0] = -prices[0];
for(int i =1;i<n;i++){
dp[i][1][0] = max(-prices[i],dp[i-1][1][0]);
// cout<<"dp["<<i<<"][1][0] = "<<dp[i][1][0]<<endl;
}
//开始dp
for(int i =1;i<n;i++){
cout<<"i = "<<i<<endl;
for(int t = 1;t<=k;t++){
// cout<<"t = "<<t<<endl;
// cout<<"dp[i-1][0][t] = "<<dp[i-1][0][t]<<endl;
// cout<<"dp[i-1][1][t-1]+prices[i] = "<<dp[i-1][1][t-1]+prices[i]<<endl;
dp[i][0][t] = max(dp[i-1][0][t],dp[i-1][1][t-1]+prices[i]);
// cout<<"do[0] = "<<dp[i][0][t]<<endl;
if(t==k){
dp[i][1][t] = INT_MIN;
}else{
// cout<<"dp[i-1][1][t] = "<<dp[i-1][1][t]<<endl;
// cout<<"dp[i-1][0][t]-prices[i] = "<<dp[i-1][0][t]-prices[i]<<endl;
dp[i][1][t] = max(dp[i-1][1][t],dp[i-1][0][t]-prices[i]);
// cout<<"dp[1] = "<<dp[i][1][t]<<endl;
}
}
}
// int ret = 0;
// for(int i = 0;i<=k;i++){
// ret = max(ret,dp[n-1][0][i]);
// }
// return ret;
return dp[n-1][0][k];
}
};
// 2
// [2,1,4,5,2,9,7]
踩坑:注意dp[I][1][k]不一定就是当天购买的,可能是前面任意一天购买的。