Leetcde——121 Best Time to Buy and Sell Stock && 123 Best Time to Buy and Sell Stock

来源:互联网 发布:js 随机掉落 编辑:程序博客网 时间:2024/05/21 01:52

本周学习的算法是动态规划dynamic programming(DP),在写题目之前,我想先比较下贪心算法,分治算法和DP算法。

  • 贪心算法Greedy:按照局部最优的标准递增地建立解决方法;
  • 分治算法Divide and Conquer:将一个问题分成一个个独立的子问题,解决每个子问题,并将子问题的解决方案组合成原问题的解决方案;
  • 动态规划DP:将一个问题分解成一组重叠的子问题,建立起更大的问题。DP适合的问题具有的属性包括两个,一是重叠子问题,二是最优子结构。
动态规划有两种方法,一种是自顶向下top-down,将问题循环地发散,如果问题的解决方案循环地使用某个子问题的解决方案,且子问题是重叠的,那么通过简单的记录(memorize)或者存储子问题的结果即可。当解决一个新的子问题时,首先查表,如果存在则直接使用,如果不存在则解决该子问题并加入表中。另一种方法是自底向上bottom-up,从子问题开始解决,使用子问题的结果来构建较大问题的解决方案。


这周要做的是动态规划DP的问题。我一直觉得DP问题很难,因为我觉得找到动态转移方程是一件困难的事,可能是之前分治问题和贪心问题的基础就不怎么好,所以DP问题就觉得更难,其次也是做的题目比较少,不能熟练地使用DP的思想。下面记录两道关于DP的题,一道是Easy,一道是Hard,都是Best Time to Buy and Sell Stock。


一.Best Time to Buy and Sell Stock(121)

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.

题目意思是,给一个数组,第i个元素表示在第i天的股票价格。如果最多进行一次交易(即先买然后卖),设计算法找出最大的收益。

例子1:

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

最大差值 : 6 - 1 = 5(不是7 - 1 = 6,因为先买了才能卖)

 

例子2:

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

在这个情况下,没有进行交易,所以最大差值是0。

 

使用DP的思想,首先考虑如何将大问题化为小的问题。假设数组a[i-1]的最大收益是p,那么现在到第i个数,a[i]的最大收益是多少?我们需要分情况讨论:

1)如果a[i]时卖出,那么此时a[0....i-1]中的股价最低价为min,那么用a[i]-min就可以得到最大的收益值,如果该值大于p,说明a[i]-min就是数组a[1....i]的最大收益。

2)如果a[i]是买入,那么其对应的卖出点应该是j >= i,对于a[0...i]来说,p依旧是该区间的最大收益值。但是要注意,a[i]必须要小于a[1....i-1]区间中的股价最低点min(1 <= k <= i-1),要不然就会出现a[j] - min > a[j] - a[i],这与a[i]是全局最大收益的买入点是有矛盾的。

3)如果不满足上述两种情况,则a[i]既不是买入点,也不是卖出点。

 

因此,算法的描述是:

  • 初始化区间股价最低点价格min_p = INT_MAX, 最大收益是max_p = 0
  • 令i从1到size递增;
  • 判断min_p和a[i]的大小关系,取小的作为min_p;
  • 判断max_p和prices[i] - min_p的关系,取大的一个作为max_p;
  • 依次递推,直到数组尾部结束为止,max_p就是最大值。

 代码展示

int maxProfit_I(vector<int>& prices) {    int size = prices.size();    int max_p = 0;    int min_p = INT_MAX;    for(int i = 0; i < size; i ++)    {    min_p = min(min_p, prices[i]);    max_p = max(max_p, prices[i] - min_p);    }    return max_p;}


二.Best Time to Buy and Sell Stock III(123)

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天的股票价格。如果最多进行两次次交易(即先买然后卖,再买然后再卖),设计算法找出最大的收益。

 

按照DP的思想,考虑这里的状态设定是什么?最多两次,也就是可以只有一次,也可以是两次,如果要第二次买入,那之前一定要先到达第一次卖出的状态。因此假设a[i]是第二个买入点,那么前面i-1项中必须找到一个最大的收益点。这里的想法是,将数组一分为二,考虑两个子数组的最大收益值,加起来求最大值。子数组求最大收益可以使用第一题的方法,将第一题的函数定义为int maxProfit_I(vector<int>& prices, int s, int e),s和e分别表示选取prices的起始和终止。

算法描述是:

  • i循环从0开始递增到size
  • 子数组10i,调用maxProfit_I,得到最大收益值m1
  • 子数组2isize,调用maxProfit_I,得到最大收益值m2
  • 每次循环都比较m1+m2和之前的最大值,记为res
  • 依次递推,直到数组尾部结束为止,res就是最大值。

提交结果,超时。毕竟要调用2*size次的maxProfit_I函数。当size很大时,复杂度还是比较大。所以只能考虑其他方法。

 

如果要第二次买入之前,一定要先到达第一次卖出的状态。假设我们把四次操作看成四个状态,进行状态的转换计算。这里考虑四种状态是第一次买,第一次卖,第二次买,第二次卖。用一个4个元素的数组来表示。假设在a[i-1]处的四个状态,cur[0]是第一次买入时的收益;cur[1]是第一次卖出的收益;cur[2]是第二次买入的收益,cur[3]是第二次卖出的收益。计算下一个四状态时,要保证每一次状态转移都是取最大的值。

 

算法描述:

  • 初始化状态cur{INT_MIN, 0, INT_MIN, 0}
  • 表示令i循环从0size
 a[i]四个状态next分别计算如下

next[0]有两种情况,要么替换第一次买的,要么不变,则next[0] = max(cur[0], -price[i])

next[1]有两种情况,要么不卖,保持不变,要么卖掉,则next[1] = max(cur[1], cur[0] + price[i]);

next[2]有两种情况,要么不买,保持不变,要么买下,在第一买卖收益的情况下减掉买下的钱,则next[2] = max(cur[2], cur[1] - price[i]);

next[3]有两种情况,要么不卖,保持不变,要么卖掉,则next[3] = max(cur[3], cur[2] + price[i]);

计算4个状态后,将next变为cur

  • 结束循环。因为最多两次交易,所以cur[1]cur[3]中的最大值为结果。


代码展示:

int maxProfit(vector<int>& prices) {int res = 0;int cur[4] = {INT_MIN, 0, INT_MIN, 0};int next[4];for(int i = 0; i < prices.size(); i ++){next[0] = max(cur[0], -prices[i]);next[1] = max(cur[1], cur[0] + prices[i]);next[2] = max(cur[2], cur[1] - prices[i]);next[3] = max(cur[3], cur[2] + prices[i]);for(int j = 0; j < 4; j ++)cur[j] = next[j];}res = max(cur[1], cur[3]);return res;}

总结:题2算法也就是状态少的时候能用,如果状态多了,估计就不能用数组来表示了。




0 0