leetcode上的股票买卖问题也属于比较经典的动态规划问题,其实上面的几道股票问题实际上思路是相同的,只不过处理时,状态繁简不一。从第一题的交易次数为1作为入门级引入此类问题,再到后来的步步深化,无不体现着动态规划的精妙之处。
在此类问题中,我们还是按照之前所说的思路,找【状态】,做【选择】,择优处理,返回最终结果。
在此题中,我们思考其状态可以得知此题的状态有:1.天数 2.该天是否持有股票
3.可交易的次数
做出的选择是:*“买入”*或者“卖出”票,且要注意的是:手上持有股票时才能卖出,且只有交易次数大于0时才能买入。
现在我们初步可以知道需要用一个三维数组来表示状态dp[][][],第三个维度用来表示股票的持有状态,用0表示未持有股票,1表示持有股票。
则dp[3][2][1]表示现在是第三天,总共能进行2次交易,且现在我持有股票。(记住如何解释「状态」,⼀旦你觉得哪⾥不好理解,把它翻译成⾃然语⾔就 容易理解了)
现在我们初步可以知道需要用一个三维数组来表示状态dp[][][],第三个维度用来表示股票的持有状态,用0表示未持有股票,1表示持有股票。
则dp[3][2][1]表示现在是第三天,总共能进行2次交易,且现在我持有股票。
(记住如何解释「状态」,⼀旦你觉得哪⾥不好理解,把它翻译成⾃然语⾔就 容易理解了)我们想求的最终答案是 dp[n - 1][K][0],即最后⼀天,最多允许 K 次交易, 最多获得多少利润。因为 [1] 代表⼿ 上还持有股票,[0] 表⽰⼿上的股票已经卖出去了,很显然后者得到的利润 ⼀定⼤于前者。
下面就是思考如何进行状态转移,dp[i][K][0](现在是第i天且未持有股票)和dp[i][K][1] (现在是第i天且持有股票)这两种状态如何由之前的状态转化而来。
我们容易想到的是前一天和今天之间的状态是联系最紧密的,
那么如何由前一天的状态转化到今天呢?经过一番思索我们得到如下状态转移方式(先用自然语言写出来然后再转化为代码形式)**dp[i][K][0]=(昨天就未持有股票且今天不进行交易则今天还是未持有股票OR昨天持有股票但今天卖出则今天变为未持有股票)。
****dp[i][K][1]=(昨天就持有股票今天不进行交易则今天还是持有股票OR昨天未持有股票但今天买入则今天变为持有股票)。
**下面将其转化为代码```cppdp[i][K][0]=max(dp[i-1][K][0],dp[i-1][K][1]+prices[i]);dp[i][K][1]=max(dp[i-1][K][1],dp[i-1][K-1][0]-prices[i]);```
此处我们定义买入时交易次数减一(同理可以定义卖出时交易次数减一,结果是一样的)
我们想求的最终答案是 dp[n - 1][K][0],即最后⼀天,最多允许 K 次交易, 最多获得多少利润。因为 [1] 代表⼿ 上还持有股票,[0] 表⽰⼿上的股票已经卖出去了,很显然后者得到的利润 ⼀定⼤于前者。
下面就是思考如何进行状态转移,dp[i][K][0](现在是第i天且未持有股票)和dp[i][K][1] (现在是第i天且持有股票)这两种状态如何由之前的状态转化而来。
我们容易想到的是前一天和今天之间的状态是联系最紧密的,那么如何由前一天的状态转化到今天呢?经过一番思索我们得到如下状态转移方式(先用自然语言写出来然后再转化为代码形式)
dp[i][K][0]=(昨天就未持有股票且今天不进行交易则今天还是未持有股票OR昨天持有股票但今天卖出则今天变为未持有股票)。
dp[i][K][1]=(昨天就持有股票今天不进行交易则今天还是持有股票OR昨天未持有股票但今天买入则今天变为持有股票)。
下面将其转化为代码
dp[i][K][0]=max(dp[i-1][K][0],dp[i-1][K][1]+prices[i]);
dp[i][K][1]=max(dp[i-1][K][1],dp[i-1][K-1][0]-prices[i]);
此处我们定义买入时交易次数减一(同理可以定义卖出时交易次数减一,结果是一样的)。
下面去哦们思考其base case:当交易次数K=0时,。下面去哦们思考其base case:当交易次数K=0时,最大利润肯定为0;即dp[-1][0][0]=dp[i][0][0]=0;
因为我们是从i=0开始(第一天开始),所当i=-1时利润为0,所以
dp[i][0][1]=dp[-1][K][1]=INT_MIN(不允许交易不可能持有股票,未开始时不会持有股票,均用负无穷来表示)。之所以要定义i=-1时的情况是因为i=0开始遍历时会出现i-1=-1的情况
下面以K=1来列出我们的代码(因为K=1是固定的,所以K不会影响最终的结果,所以可以把K去掉)
for(int i=0;i<n;i++){
if(i-1=-1){
//解释
//dp[i][0]=max(dp[-1][0],dp[-1][1]+prices[i])
//max(0,INT_MIN+prices[i])
dp[i][0]=0;
//解释
//dp[i][1]=max(dp[-1][1],dp[-1][0]-prices[i])
//=max(INT_MIN,0-prices[i])
dp[i][1]=-prices[i];
continue;
}
dp[i][0] = Math.max(dp[i-1][0], dp[i-1][1] + prices[i]);
dp[i][1] = Math.max(dp[i-1][1], -prices[i]);//这里之所以是-prices[i]是因为K=1,而前一天未持有股票而今天买入股票就对应交易次数变为0,故原本是dp[i-1][k-1][0]就=dp[i-1][0][0]恒为零
}
return dp[n-1][0];
第⼀题就解决了,但是这样处理 base case 很麻烦,⽽且注意⼀下状态转移 ⽅程,新状态只和相邻的⼀个状态有关,其实不⽤整个 dp 数组,只需要⼀ 个变量储存相邻的那个状态就⾜够了,这样可以把空间复杂度降到 O(1):
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n=prices.size();
int dp_i_0=0,dp_i_1=INT_MIN;
for(int i=0;i<n;i++){
dp_i_0=max(dp_i_0,dp_i_1+prices[i]);//注意此时两个dp_i_0是不同的含义,左边的是更新后的,而max括号里的是之前的状态,下同dp_i_1
dp_i_1=max(-prices[i],dp_i_1);
}
return dp_i_0;
}
};