这一期到了买卖股票专题,买卖股票的有一些题型,可以使用贪心算法来求解,甚至有时候比动态规划更简单一些,但是本期是讲动态规划的运用,所以不做对于贪心的分析。今天只讲两道例题,其中第二题是第一题的变种,没有做过股票系列题型,我个人认为是有一定难度解题的。
121. 买卖股票的最佳时机 - 力扣(LeetCode)https://leetcode.cn/problems/best-time-to-buy-and-sell-stock/整个数组中,我们只允许买入一只股票。给定的数组是当天的股票可以买卖多少钱,我们需要找一个合适的时机买入,再把它卖出去,以获得最大的利润。
dp数组的定义:因为是动态规划自然少不了dp数组,要想出dp数组首先并不是一件很容易的事,它不像是我们上一期的打家劫舍,可以用一维数组来记录盗取的最大金额,这里要选用二维数组来记录,当前天买股票我们能获得的最大金额,和卖出股票我们能获得的最大金额。我个人的见解是,由于我们一开始不能很快确定要哪一天买进和卖出,而且买进和卖出是应该分离开来,以便获得最大利润,所以不能用一维数组来实现。股票是否已经被买入或未被买入我们是需要一直保持记录的,它不像是打家劫舍,这一次偷盗只关乎着偷不偷上一家所得到的金额。有点扯远了,总之我们要使用二维数组,且dp【i】【0】代表了,当前第i天持有股票的状态且能获得的最大钱,dp【i】【1】代表了卖出股票所能获得最大金额,注意这里的持有不仅仅包含当天买入股票,以前买了股票,现在还没卖,那一样是拥有着股票,dp【i】【1】代表了卖出股票所能获得的最大利润。至于为什么要使它的含义表示的范围那么大,为什么不能直接的表示买入或者卖出,这是因为这样表示方便其他数组的填写,不然还要定义持有和卖完之后的情况来填满数组比较麻烦。
递推公式:递推公式是针对于两种不同状态的两个递推公式,第一个是针对持有股票的状态,我们还需要搞清一点,如何更新该值?我们知道只能购买一支股票,所以我们可以假定我们一开始有的钱数是0,买了股票是负数,卖出去了赚了钱才是正数。根据这一理念,我们需要比较的是当天如果买股票就是负的prices【i】,前一天的状态中如果持有股票,最大金额是dp【i-1】我们取它们中最大的值。前一天状态的持有股票也并不是一定是那一天买入的,而是这些天里最便宜时候买入的。
对于第二个递推公式,是尤为重要的,它是卖出股票的递推公式,而卖出股票的递推公式同样也是受两个值影响,一个是它的前一天如果卖出了股票所能获得最大钱,另一个是如果已经购买过股票的前提下,那么今天卖出去可以获得多少钱。
得出:dp【i】【0】=max(dp【i-1】【0】,-prices【i】)
dp【i】【1】=max(dp【i-1】【1】,prices【i】+dp【i】【0】)
解答一些疑问:我们如何知道已经购得股票。其实这并不需要额外的变量记载,标志,我们是否购得,这实际上是由dp【i】【0】一直在记录的,遍历的0-i之间,那一天卖的便宜哪一天就买了,同样的dp【i】【1】是管卖的,哪一天贵了得到的利润高了就会覆盖当前值,也即是两个部分是即时更新的。
而且也不会出现还没有买就卖的情况,因为i是不会向前走的,而卖股票的递推公式它也是和i有关,所以不会现在便宜买了之后,向前找贵的天卖。
dp数组初始化:dp【0】【0】应该是最开始的状态持有股票,我们把它初始化为-prices【0】。
有人要问了,第一天就买股票它如果不是最便宜一天怎么办?大家不要这样理解,因为我们会更新这个数字,但是不能将它一开始就初始化为0,这是因为上面已经说过了,初始金钱是0,你这里还初始化为0,那它永远也不会购入股票了。一直会保持0,因为只要购入股票就会得到负数比0小!!!dp【0】【1】初始化为0,因为刚第一天没有卖股票,也卖不了股票。
遍历顺序:遍历顺序自然是从前向后遍历
class Solution {
public:
int maxProfit(vector<int>& prices) {
if(prices.size()<=1)return 0;
vector<vector<int>>dp(prices.size(),vector<int>(2));
dp[0][0]=-prices[0];dp[0][1]=0;
for(int i=1;i<prices.size();i++){
dp[i][0]=max(dp[i-1][0],-prices[i]);
dp[i][1]=max(dp[i-1][1],prices[i]+dp[i][0]);
}
return dp[prices.size()-1][1];
}
};
122. 买卖股票的最佳时机 II - 力扣(LeetCode)https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-ii/这道题和第一道题唯一的区别在于虽然股票也限定只能同时持有一支,但是在你卖出手中的股票之后,仍然可以再次购买股票,这就是和上一道题不同之处,很多人看到这里就懵了,感觉这道题很难做,其实只是上一道题改一点点代码就可以了。
这道题的dp数组定义和初始化遍历顺序都是一样的,只有递推公式求买入股票的部分不一样。在没看题解的时候,我以为递推公式这道题是只有一个的,而且可以累加的,但是其实并不是,它是通过卖出股票对买入股票的钱产生影响,从而影响了卖出股票的利润,这道题在贪心算法的运用上,思路是求取只要利润为正都相加在一起,而dp来实现思路上还是有所不同的。
递推公式:do【i】【0】=max(dp【i-1】【0】,dp【i】【1】-prices【i】)
只有这一点改动,就可以完成对于股票的重复买卖的操作,最后返回dp【prices。size()-1】【1】就可以了,为什么是这样呢?因为买入股票的第一个比较的参数,是不变的影响它的改变的参数是第二个,第二个参数是卖出股票后手里的钱如果买了当前的股票还足够多的话(比当前持有股票的钱多)那么就更新它,而卖出股票也是和当下卖的股票钱数有关,两个递推公式彼此都有关系,就造成了互相影响更新最大值,所以自然不需要累加也可以完成类似累加的操作。
class Solution {
public:
int maxProfit(vector<int>& prices) {
vector<vector<int>>dp(prices.size(),vector<int>(2));
dp[0][0]=-prices[0];
dp[0][1]=0;
for(int i=1;i<prices.size();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-0][0]+prices[i]);
}
return dp[prices.size()-1][1];
}
};
我觉得是挺神奇的,大家好好体会一下。还有为什么第一道题的持有股票的递推公式第二个参数只是-prices【i】,我想再做一下解释。第一道题是只能买一次股票,我们是假设了初始兜里只有0元钱,而这道题可以买了再卖再买入,所以我们用的是dp【i-1】【1】,而这是什么呢?我们也可以理解为,这是目前为止我们卖股票赚的利润,用它再来买新的股票,如果不是很亏,我们就用剩下的钱减去当天的股票钱。这里的不是很亏的评价标准,也就是之前持有股票的时候拥有的钱多,还是之前未持有股票但是今天购买了拥有的钱多。这并不会一直不买,因为后面还有一句卖出股票,只要利润比你买股票大,那肯定就卖出去了,这就实现了和贪心差不多的思路,找正利润的累加起来。
第二道题,我认为有一些抽象的,需要好好理解,不要把它当作一道太简单的题来看待。