欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 新闻 > 资讯 > Day43 | 动态规划 :状态机DP 买卖股票的最佳时机买卖股票的最佳时机II

Day43 | 动态规划 :状态机DP 买卖股票的最佳时机买卖股票的最佳时机II

2024/11/15 9:39:23 来源:https://blog.csdn.net/m0_74795952/article/details/143747384  浏览:    关键词:Day43 | 动态规划 :状态机DP 买卖股票的最佳时机买卖股票的最佳时机II

Day43 | 动态规划 :状态机DP 买卖股票的最佳时机&&买卖股票的最佳时机II

动态规划应该如何学习?-CSDN博客

本次题解参考自灵神的做法,大家也多多支持灵神的题解

买卖股票的最佳时机【基础算法精讲 21】_哔哩哔哩_bilibili

动态规划学习:

1.思考回溯法(深度优先遍历)怎么写

注意要画树形结构图

2.转成记忆化搜索

看哪些地方是重复计算的,怎么用记忆化搜索给顶替掉这些重复计算

3.把记忆化搜索翻译成动态规划

基本就是1:1转换

文章目录

  • Day43 | 动态规划 :状态机DP 买卖股票的最佳时机&&买卖股票的最佳时机II
    • 121.买卖股票的最佳时机
      • 思路分析(子问题):
        • 重点:
          • 如果我第i天持有股票,status==1,dfs(i,1)
          • 如果我第i天没有股票,status==0,dfs(i,0)
      • 1.回溯 DFS
      • 2.记忆化搜索
      • 3.1:1翻译为动态规划
    • 122.买卖股票的最佳时机II
      • 1.回溯法
      • 2.记忆化搜索
      • 3.动态规划

121.买卖股票的最佳时机

121. 买卖股票的最佳时机 - 力扣(LeetCode)

思路分析(子问题):

最后一天发生了什么?

输入:[7,1,5,3,6,4]
输出:5

从低0天开始到第五天结束时的利润=从第0天开始到第四天结束时的利润+第五天的利润

第五天的利润有三种可能(就光说第五天这一天)

1.我啥也没干那就是0,因为我没有买进也没有卖出

2.我把我有的股票给卖了,那就是4,即prices[5]

3.我本来就没股票,我今天买入了,那就是-4,即-prices[5]

为什么是负的利润还要买?因为你卖的前提是你买了。

然后就是熟悉的往前推,从0到第四天结束的利润是前三天的利润+第四天的直到第0天

可以发现我们需要一个bool值来体现我们现在是否持有股票,false表示没有,true表示有

大家要注意理解:

我第i-1天的结束就是第i天的开始

重点:

dfs(i,status)的含义就是我从第0天到第i天,在status状态下所能得到的最大金额,status就是咱们提到的bool值0或1

如果我第i天持有股票,status==1,dfs(i,1)

那我第一个可能是我第i-1天就有股票但是我没卖掉,即i-1天的时候也有股票,那我在从0-i这几天的最大金额就肯定是dfs(i-1,1)

也有可能是我在第i天的时候买了,那说明我前面都没买也没卖,那前i-1天的总利润就是0,第i天的利润就是-prices[i]

dfs(i,1)=max(dfs(i-1,1),-prices[i])
如果我第i天没有股票,status==0,dfs(i,0)

那我第一个可能是我第i-1天就没有股票,然后今天也没买,那我在从0到i这几天的最大金额就肯定是dfs(i-1,0)

也有可能是我在0到i天中间买了股票了,一直没卖,到了今天才卖掉了,那我第i天的利润就是dfs(i-1,1)+prices[i],因为你得保证你卖出的时候你的前一天是有股票的,不然卖啥啊对吧

(买的时候花的钱已经算到买的那天的利润里面去了,是负的)

dfs(i,0)=max(dfs(i-1,0),dfs(i-1,1)+prices[i])

最后返回的是什么,那肯定是最后一天不持有股票的情况,就是我们要的最大值了

image-20241113163933787

这样的表示状态转移的图叫做状态机,从图中可以明显的看出状态的转移

大家把买入部分的dfs(i-1,0)当做0,就是本题的状态转移,正如上面所说,dfs(i-1,0)说的是我前一天没有股票的最大利润,这题里面我只要i-1天没有股票,那说明我前面都没买过股票,利润只能是0

image-20241113164118781

结合灵神的图更好理解

image-20241113164358703

1.回溯 DFS

1.返回值和参数

i是每天的价格

status是状态,表示是否持有股票

int dfs(int i,int status,vector<int>& prices)

2.终止条件

dfs(-1,0)表示前-1天不持有股票的利润,那自然是零

dfs(-1,1)表示前-1天持有股票的利润,这显然是不合法的状态,因为前-1天的时候还不能买股票,更不能卖,所以返回负无穷

		if(i<0)if(status==true)   return INT_MIN;else    return 0;

3.本层逻辑

如递推公式所说,我有股票,那就从有股票的两种情况选个最大的

没有股票就从没有股票的两种情况选个最大的

		if(status==1)return max(dfs(i-1,1,prices),-prices[i]);else    return max(dfs(i-1,0,prices),dfs(i-1,1,prices)+prices[i]);

完整代码:

当然,这是超时的

class Solution {
public:int dfs(int i,int status,vector<int>& prices){if(i<0)if(status==true)   return INT_MIN;else    return 0;if(status==1)return max(dfs(i-1,1,prices),-prices[i]);else    return max(dfs(i-1,0,prices),dfs(i-1,1,prices)+prices[i]);}int maxProfit(vector<int>& prices) {return dfs(prices.size()-1,0,prices);}
};

2.记忆化搜索

就是搞一个哈希表dp,全都初始化为-1,每次返回前给哈希表dp赋值,碰到不是-1的那就是算过的,那就直接返回计算过的结果,不需要再次递归了

class Solution {
public:int dfs(int i,int status,vector<int>& prices,vector<vector<int>>& dp){if(i<0)if(status==true)   return INT_MIN;else    return 0;if(dp[i][status]!=-1)return dp[i][status];if(status==1)return dp[i][status]=max(dfs(i-1,1,prices,dp),-prices[i]);else    return dp[i][status]=max(dfs(i-1,0,prices,dp),dfs(i-1,1,prices,dp)+prices[i]);}int maxProfit(vector<int>& prices) {vector<vector<int>> dp(prices.size(),vector<int>(2,-1));return dfs(prices.size()-1,0,prices,dp);}
};

3.1:1翻译为动态规划

1.确定dp数组以及下标的含义

dp数组是前i天可以取得的最大利润

下标笔者采用dp的i对应prices的i-1,防止dp数组下标出现负数(影响的知识dp数组中的位置,对结果不影响的)

也会给出dp数组和prices下标对应的,即dp数组下标就是天数

2.确定递推公式

//第i天没股票
dp[i][0]=max(dp[i-1][0],dp[i-1][1]+prices[i]);
//第i天有股票
dp[i][1]=max(dp[i-1][1],-prices[i]);
dp[i+1][0]=max(dp[i][0],dp[i][1]+prices[i]);
dp[i+1][1]=max(dp[i][1],-prices[i]);

3.dp数组如何初始化

DP数组下标从0开始

第0天没有股票,利润为0,dp[0][0]=0;

第0天有股票,那肯定买的第0天的,利润为-prices[i],dp[0][1]=-prices[i];

DP数组下标从1开始

dfs中的第-1天对应dp的第0天

//dfs(-1,0)对应dp[0][0]
//dfs(-1,1)对应dp[0][1]
dp[0][0]=0;
dp[0][1]=INT_MIN;

和回溯中终止条件对应

4.确定遍历顺序

后续结果需要依赖前面的计算结果,故使用从前往后遍历

		for(int i=0;i<prices.size();i++){dp[i+1][0]=max(dp[i][0],dp[i][1]+prices[i]);dp[i+1][1]=max(dp[i][1],-prices[i]);}   

完整代码

//避免出现负数下标的情况 dp下标从1开始
class Solution {
public:int maxProfit(vector<int>& prices) {vector<vector<int>> dp(prices.size()+1,vector<int>(2,0));dp[0][0]=0;dp[0][1]=INT_MIN;for(int i=0;i<prices.size();i++){dp[i+1][0]=max(dp[i][0],dp[i][1]+prices[i]);dp[i+1][1]=max(dp[i][1],-prices[i]);}   return dp[prices.size()][0];}
};
//dp下标从0开始
class Solution {
public:int maxProfit(vector<int>& prices) {int len = prices.size();if (len == 0) return 0;vector<vector<int>> dp(len, vector<int>(2));dp[0][0] -= prices[0];dp[0][1] = 0;for (int i = 1; i < len; i++) {dp[i][0] = max(dp[i - 1][0], -prices[i]);dp[i][1] = max(dp[i - 1][1], prices[i] + dp[i - 1][0]);}return dp[len - 1][1];}
};

122.买卖股票的最佳时机II

122. 买卖股票的最佳时机 II - 力扣(LeetCode)

image-20241113163933787

这样的表示状态转移的图叫做状态机,从图中可以明显的看出状态的转移

image-20241113164118781

结合灵神的图更好理解

image-20241113164358703

和上一题的区别就是,这里的股票可以买卖多次

体现在如果我第i天有股票的话,我可能前面买过股票

 dfs(i,1)=max(dfs(i-1,1,prices),dfs(i-1,0,prices)-prices[i]);

而上一题是

dfs(i,1)=max(dfs(i-1,1),-prices[i])

直接写-prices[i](其实是0-prices[i])是因为如果你在第i天买进,前面肯定没买过,因为上一题只能买一次,0到i-1天的最大利润只可能是0

本题是前面可能买卖过,可能0到i-1天的最大利润不为0

1.回溯法

class Solution {
public:int dfs(int i,int status,vector<int>& prices){if(i<0)if(status==1)return INT_MIN;elsereturn 0;if(status==1)return max(dfs(i-1,1,prices),dfs(i-1,0,prices)-prices[i]);elsereturn max(dfs(i-1,0,prices),dfs(i-1,1,prices)+prices[i]);}int maxProfit(vector<int>& prices) {return dfs(prices.size()-1,0,prices);}
};
//lambda
class Solution {
public:int maxProfit(vector<int>& prices) {function<int(int,int)> dfs=[&](int i,int status)->int{if(i<0)if(status==1)return INT_MIN;elsereturn 0;if(status==1)return max(dfs(i-1,1),dfs(i-1,0)-prices[i]);elsereturn max(dfs(i-1,0),dfs(i-1,1)+prices[i]);};return dfs(prices.size()-1,0);}
};

2.记忆化搜索

class Solution {
public:int dfs(int i,int status,vector<int>& prices,vector<vector<int>>& dp){if(i<0)if(status==1)return INT_MIN;elsereturn 0;if(dp[i][status]!=-1)return dp[i][status];if(status==1)return dp[i][status]=max(dfs(i-1,1,prices,dp),dfs(i-1,0,prices,dp)-prices[i]);elsereturn dp[i][status]=max(dfs(i-1,0,prices,dp),dfs(i-1,1,prices,dp)+prices[i]);}int maxProfit(vector<int>& prices) {vector<vector<int>> dp(prices.size(),vector<int>(2,-1));return dfs(prices.size()-1,0,prices,dp);}
};
//lambda
class Solution {
public:int maxProfit(vector<int>& prices) {vector<vector<int>> dp(prices.size(),vector<int>(2,-1));function<int(int,int)> dfs=[&](int i,int status)->int{if(i<0)if(status==1)return INT_MIN;elsereturn 0;if(dp[i][status]!=-1)return dp[i][status];if(status==1)return dp[i][status]=max(dfs(i-1,1),dfs(i-1,0)-prices[i]);elsereturn dp[i][status]=max(dfs(i-1,0),dfs(i-1,1)+prices[i]);};return dfs(prices.size()-1,0);}
};

3.动态规划

这里的dp数组下标是从0开始的

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

和记忆化搜索对应的

dp数组下标从1开始,避免了负数下标的状态

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

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com