您的位置:首页 > 其它

[LeetCode]Buy and Sell Stocks 买卖股票问题

2016-09-10 15:22 204 查看
LeetCode上关于买卖股票的问题一共有五道,题号分别为121,122,123,188,309。

此类问题的基本描述即给出一个序列prices[],prices[i]代表第i天股票的价格。

如果当天之前不持有股票,则可以以prices[i]的价格购入股票;

如果当天之前持有股票,则可以以prices[i]的价格卖出股票;

当然,当天也可任何操作都不进行。

目的就是求该阶段的最大收益。

接下来我们根据每道题的约束条件不同,从易到难逐个分析这五个问题。

一、Best Time
to Buy and Sell Stock  

Say you have an array for which the ith element is the price of a given stock on day i.

If you were only permitted to complete at most one transaction (ie, buy one and sell one share of the stock), design an algorithm to find the maximum profit.

Example 1:

Input: [7, 1, 5, 3, 6, 4]
Output: 5

max. difference = 6-1 = 5 (not 7-1 = 6, as selling price needs to be larger than buying price)


Example 2:

Input: [7, 6, 4, 3, 1]
Output: 0

In this case, no transaction is done, i.e. max profit = 0.


本问题的约束即整个过程只能进行一次买卖操作。

采取两个变量in和out来记录第i买入stock的最小代价和卖出stock的最大价格,如果 out-in>ans,则将ans更新为out-in。

时间复杂度O(n),空间复杂度O(1)

public int maxProfit(int[] prices) {
int len = prices.length;
if (len == 0 || len == 1) return 0;
int in = prices[0], out = prices[0], ans = 0;
for (int i = 1; i < len; i++) {
in = Math.min(in, prices[i]);
out = Math.max(in, prices[i]);
ans = out-in>ans ? out-in:ans;
}
return ans;
}

二、Best
Time to Buy and Sell Stock II  

Say you have an array for which the ith element is the price of a given stock on day i.

Design an algorithm to find the maximum profit. You may complete as many transactions as you like (ie, buy one and sell one share of the stock multiple times). However, you may not engage in multiple transactions at the same time (ie, you must sell the stock
before you buy again).

本问题即可以尽可能多的进行买卖操作,但是还是要保证买之前不持stock,卖之前持有stock。
此时该问题转化为一个贪心问题(五个问题中唯一一个贪心,其他都是DP),贪心准则很简单:只要当天比前一天股价高,则以前一天的价格买入并且当天卖出。

看似存在两个问题:

1. 题目要求当天不能同时买卖,如果对于 [1, 3, 7] 这样的情况,是不是违背了约束?当然不是,对于这样的单调序列,无论内部长度多长,最大收益始终由首和尾两个元素确定,而该收益完全等价于相邻两天价格差的和。

2. 对于[1, 5, 3, 9]这样的非单调序列,是否找到的是真正的最大收益?是的,对于任意一个非单调序列,必然由若干个单调增序列和若干个单调减序列组成,故可以找到最大收益。

解决了这两个问题,可有时间复杂度O(n),空间复杂度O(1)的代码:

public int maxProfit(int[] prices) {
int len = prices.length;
if (len < 2) return 0;
int ans = 0;
for (int i = 1; i< len; i++) {
if (prices[i]>prices[i-1]) ans += prices[i]-prices[i-1];
}
return ans;
}


三、Best
Time to Buy and Sell Stock III  

Say you have an array for which the ith element is the price of a given stock on day i.

Design an algorithm to find the maximum profit. You may complete at most two transactions.

Note:

You may not engage in multiple transactions at the same time (ie, you must sell the stock before you buy again).

本问题相对于I,将只能完成一笔买卖操作的约束更改为至多可以完成两笔买卖操作。

比较直观的想法是找出这样一个断点i,该断点前记为第一笔买卖,该断点后记为第二笔买卖。

可以借用I中的想法正向算出各个时间段的第一笔买卖的最大收益;

对于各个时间段第二笔买卖的最大收益可以采取反向的方法:从prices[]数组尾端开始向前扫描,用out2记录能卖出的最高价,注意此时只能以prices[i]的价格买入,而不能用in2记录能买入的最低价,比如[2, 6,1]这样的序列就会有问题。

这样的想法的时间复杂度为O(n),空间复杂度也为O(n):

public int maxProfit(int[] prices) {
int len = prices.length;
if (len == 0 || len == 1) return 0;
int in1 = prices[0], out1 = prices[0];
int in2 = prices[len-1], out2 = prices[len-1];
int[] ans1 = new int[len];
int[] ans2 = new int[len];
for (int i = 1, j = len-2; i < len; i++, j--) {
in1 = Math.min(in1, prices[i]);
out1 = Math.max(in1, prices[i]);
ans1[i] = out1-in1>ans1[i-1] ? out1-in1:ans1[i-1];

in2 = prices[j];
ans2[j] = out2-in2>ans2[j+1] ? out2-in2:ans2[j+1];
out2 = Math.max(out2, prices[j]);
}
int ans = Math.max(ans1[len-1], ans2[0]);
for (int i = 1; i < len-1; i++) {
ans = Math.max(ans1[i]+ans2[i+1], ans);
}
return ans;
}

下面介绍一下别人时间复杂度为O(n),空间复杂度为O(1)的方法:

分别用hold1、hold2记录第一笔和第二笔买卖购入时的手中最大的钱数,用release1、release2记录第一笔和第二笔买卖卖出时的手中最大的钱数。

则hold1即取时刻i之前的最低股票价即可,release1取第i时刻之前的hold1+prices[i]和release1大者;

hold2取时刻i之前完成第一笔买卖即release1减去当前股票价格和hold2大者,release2用release1的方法更新即可。

需要注意的是每个时刻这四个变量更新的顺序。

public int maxProfit(int[] prices) {
int hold1 = Integer.MIN_VALUE, hold2 = Integer.MIN_VALUE;
int release1 = 0, release2 = 0;
for(int i:prices){                              // Assume we only have 0 money at first
release2 = Math.max(release2, hold2+i);     // The maximum if we've just sold 2nd stock so far.
hold2    = Math.max(hold2,    release1-i);  // The maximum if we've just buy  2nd stock so far.
release1 = Math.max(release1, hold1+i);     // The maximum if we've just sold 1nd stock so far.
hold1    = Math.max(hold1,    -i);          // The maximum if we've just buy  1st stock so far.
}
return release2; ///Since release1 is initiated as 0, so release2 will always higher than release1.
}


四、Best Time to Buy
and Sell Stock IV  

Say you have an array for which the ith element is the price of a given stock on day i.

Design an algorithm to find the maximum profit. You may complete at most k transactions.

Note:

You may not engage in multiple transactions at the same time (ie, you must sell the stock before you buy again).

相对于前几个问题,本问题进行可扩展,要求最多可以进行k笔买卖操作。
对于k大于等于len/2的情况,该问题化为问题二的贪心;

对于k小于len/2的情况,假设我们已经得到最大买卖笔数为k-1的问题的解,则在时刻i,假设最大买卖笔数为k的前i-1时刻的最大收益记录在record[k][i-1]中,用currCost记录在手中握有股票的情况下的最大收益,则最大买卖笔数为k的第i时刻的最大收益 record[k][i] = max(record[k][i-1], currCost+prices[i]),即要么为上一时刻的最大收益,要么为上一时刻持有股票情况下在时刻i买出的收益之和。对于currCost的更新,也要满足currCost最大化,故它应该取record[k-1][i-1]-prices[i]和currCost的较大者。

于是可以得到时间复杂度为O(k*n),空间复杂度为O(k*n)的方法:

public int maxProfit(int k, int[] prices) {
int len = prices.length;
if (k >= len/2) return helper(prices);
int[][] record = new int[k+1][len];
for (int i = 1; i <= k; i++) {
int currCost = -1*prices[0];
for (int j = 1; j < len; j++) {
record[i][j] = Math.max(record[i][j-1], currCost+prices[j]);
currCost = Math.max(currCost, record[i-1][j-1]-prices[j]);
}
}
return record[k][len-1];
}
public int helper(int[] prices) {
int len = prices.length;
int profit = 0;
for (int i = 1; i < len; i++) {
if (prices[i] > prices[i-1])
profit += prices[i]-prices[i-1];
}
return profit;
}


五、Best
Time to Buy and Sell Stock with Cooldown  

Say you have an array for which the ith element is the price of a given stock on day i.

Design an algorithm to find the maximum profit. You may complete as many transactions as you like (ie, buy one and sell one share of the stock multiple times) with the following restrictions:
You may not engage in multiple transactions at the same time (ie, you must sell the stock before you buy again).
After you sell your stock, you cannot buy stock on next day. (ie, cooldown 1 day)

Example:

prices = [1, 2, 3, 0, 2]
maxProfit = 3
transactions = [buy, sell, cooldown, buy, sell]


本问题在可以进行多次买卖操作的情况下加入了cooldown状态,即要求卖出操作之后的第二天不能进行买操作。

引入三个数组buy[], sell[], cooldown[],分别记录第i时刻进行买、卖、保持操作所能产生的最大收益,可以得到如下关系:

buy[i] = max(cooldown[j]-prices[i]), j∈[0, i-1]

sell[i] = max(buy[j]+prices[i]), j∈[0, i-1]

cooldown[i] = max(sell[j]), j∈[0, i-1]

故有时间复杂度为O(n*n),空间复杂度为O(n)的方法:

public int maxProfit(int[] prices) {
int len = prices.length;
if (len < 2) return 0;
int[] buy = new int[len];
int[] sell = new int[len];
int[] cooldown = new int[len];
buy[0] = -prices[0];
int ans = Integer.MIN_VALUE;
for (int i = 1; i < len; i++) {
buy[i] = Integer.MIN_VALUE;
sell[i] = Integer.MIN_VALUE;
cooldown[i] = Integer.MIN_VALUE;
for (int j = 0; j < i; j++) {
buy[i] = Math.max(buy[i], cooldown[j]-prices[i]);
ans = Math.max(buy[i], ans);
sell[i] = Math.max(sell[i], buy[j]+prices[i]);
ans = Math.max(sell[i], ans);
cooldown[i] = Math.max(cooldown[i], sell[j]);
ans = Math.max(cooldown[i], ans);
}
}
return ans;
}


该方法可以优化到时间复杂度为O(n),空间复杂度为O(1):

观察到任意时刻总有sell[i]大于等于cooldown[i],故不再使用cooldown数组,而是借用

buy[i] = max(buy[i-1], sell[i-2]-prices[i])

sell[i] = max(sell[i-1], buy[i-1]+prices[i])

可见buy[i]和sell[i]只依赖于i-1时刻和i-2时刻。

public int maxProfit(int[] prices) {
int sell = 0, prev_sell = 0, buy = Integer.MIN_VALUE, prev_buy;
for (int price : prices) {
prev_buy = buy;
buy = Math.max(prev_sell - price, prev_buy);
prev_sell = sell;
sell = Math.max(prev_buy + price, prev_sell);
}
return sell;
}


总结:这个系列的题不算特别难,当然那个K次的还是比较恶心的,希望总结完以后再遇到DP的题不再那么怵了,有些人说DP的本质不是求解递推式,我不知道我个人的理解是否正确,最近做了一些DP的题目之后,总感觉像是一阶马尔科夫过程,有点状态转移方程的意思,如果理解上有了偏差,还请网友指正批评~
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐