买卖股票的最佳时期

Best Time to Buy and Sell Stock I

给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。

如果你最多只允许完成一笔交易(即买入和卖出一支股票),设计一个算法来计算你所能获取的最大利润

注意你不能在买入股票前卖出股票

示例 1:

输入: [7,1,5,3,6,4]
输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
     注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。

示例 2:

输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。

分析:
贪心法,分别找到价格最低和最高的一天,低进高出,注意最低的一天要在最高的一天之前。
把原始价格序列变成差分序列,本题也可以做是最大 m 子段和, m = 1。
 int maxProfit(vector<int>& prices) {
        //现在最低的时候买入,再在最高的时候卖掉
        /*把原始价格序列变成差分序列,本题也可以做是最大 m 子段和, m = 1。*/
        
        if(prices.size()<2) return 0;
        
        int profit=0;
        int cur_min=prices[0];
        for(int i=1;i<prices.size();++i){
            profit=max(profit, prices[i]-cur_min);
            cur_min=min(cur_min, prices[i]);
        }
        return profit;      
}

买卖股票的最佳时机 II

给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。

注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

分析:
贪心法,低进高出,把所有正的价格差价相加起来。
把原始价格序列变成差分序列,本题也可以做是最大 m 子段和, m = 数组长度。
int maxpfrofit2(vector<int> prices)
{
    int sum = 0;
    for (int i = 1; i < prices.size(); ++i){
        if (prices[i] - prices[i - 1]>0)
            sum += prices[i] - prices[i - 1];
    }
    return sum;
}

买卖股票的最佳时机 III

给定一个数组,它的第 i 个元素是一支给定的股票在第 天的价格。

设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。

注意: 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

分析:

设状态 f(i) ,表示区间[0, i](0 ≤ i n - 1)的最大利润,状态 g(i) ,表示区间[i- 1](0 ≤ ≤ - 1)的最大利润,则最终答案为

max {f(i) + g(i)} , 0 ≤ i n - 1
允许在一天内买进又卖出,相当于不交易,因为题目的规定是最多两次,而不是一定要两次。将原数组变成差分数组,本题也可以看做是最大 子段和, m=2 


以第i天为分界线,计算第i天之前进行一次交易的最大收益preProfit[i],和第i天之后进行一次交易的最大收益postProfit[i],最后遍历一遍,max{preProfit[i] + postProfit[i]} (0≤i≤n-1)就是最大收益。

方法一:

int maxProfit(vector<int>& prices) {
  //其实也就是找到两个最大子数组和,和I题差不多。
  //先求出第一个最大子数组,避开第一个最大子数组的情况下,再求出第二大子数组,再相加即可。
  if(prices.size()<2) return 0;

  int n=prices.size();
  vector<int> maxprofit1(n,0), maxprofit2(n,0);
  int maxprofit=0;

  for(int i=1, cur_min1=prices[0]; i<n; ++i){
    cur_min1=min(cur_min1, prices[i]);
    maxprofit1[i]=max(maxprofit1[i-1], prices[i]-cur_min1); 
  }

  for(int i=n-2, cur_max2=prices[n-1]; i>=0; --i){
    cur_max2=max(cur_max2, prices[i]);
    maxprofit2[i]=max(maxprofit2[i], cur_max2-prices[i]);
  }

  for(int i=0;i<n;++i)
    maxprofit=max(maxprofit, maxprofit1[i]+maxprofit2[i]);
  return maxprofit;
}

方法二:

  int maxProfit(vector<int>& prices) {   
    
//buy1:买第一次手里的钱;sell1卖出去一次手里的钱 = max(buy1+卖出当前股票价格的钱,上一轮卖出第一笔股票后手里剩的钱);    
    
//buy2在该价格买入第二次手里的钱 = max(上一轮买入第二笔股票后手里剩的钱,sell1-当前点股票价格)
    
//sell2在该价格卖出第二次后手里剩的钱 = max(上一轮卖出第二笔股票后手里剩的钱, buy2+当前股票价格)
int buy1=INT_MIN, buy2=INT_MIN; int sell1=0,sell2=0; for(int i=0;i<prices.size();i++){ sell2=max(sell2, buy2+prices[i]); buy2=max(buy2, sell1-prices[i]); sell1=max(sell1, buy1+prices[i]); buy1=max(buy1, -prices[i]); } return sell2; }

买卖股票的最佳时机 IV

给定一个数组,它的第 i 个元素是一支给定的股票在第 天的价格。

设计一个算法来计算你所能获取的最大利润。你最多可以完成 k 笔交易

注意: 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)

分析:
用一个局部最优解和全局最有解表示到第i天进行j次的收益,特殊的动态规划。local[i][j]为在到达第i天时最多可进行j次交易并且最后一次交易在最后一天卖出的最大利润,此为局部最优。定义global[i][j]为在到达第i天时最多可进行j次交易的最大利润,
此为全局最优。他们的递推公式为:diff = prices[i]-prices[i-1]
  local[i][j] = max(global[i - 1][j - 1] + max(diff, 0), local[i - 1][j] + diff)
  global[i][j] = max(local[i][j], global[i - 1][j])

关于 local 的状态转移方程,取下面二者中较大的一个:
  1. 全局前 i-1 天进行了 j-1 次交易,然后然后加上今天的交易产生的利润(如果赚钱就交易,不赚钱就不交易,什么也不发生,利润是0
  2. 局部前 i-1 天进行了 j 次交易,然后加上今天的差价(local[i-1][j]是第 i-1 天卖出的交易,它加上diff后变成第 i 天卖出,并不会增加交易次数。无论 diff 是正还是负都要加上,否则就不满足 local[i][j] 必须在最后一天卖出的条件了)

local[i][j]意味着在第i天一定有交易(卖出)发生,当第i天的价格高于第i-1天(即diff > 0)时,那么可以把这次交易(第i-1天买入第i天卖出)跟第i-1天的交易(卖出)合并为一次交易,

即local[i][j]=local[i-1][j]+diff;当第i天的价格不高于第i-1天(即diff<=0)时,那么local[i][j]=global[i-1][j-1]+diff,而由于diff<=0,所以可写成local[i][j]=global[i-1][j-1]

global[i][j]就是我们所求的前i天最多进行k次交易的最大收益,可分为两种情况:如果第i天没有交易(卖出),那么global[i][j]=global[i-1][j];如果第i天有交易(卖出),那么global[i][j]=local[i][j]。

  • 动规所用的二维辅助数组可以降为一维的,即只用大小为k的一维数组记录到达第i天时的局部最优解和全局最优解。需要注意的是,由于第i天时交易k次的最优解依赖于第i-1天时交易k-1次的最优解,

所以数组更新应当从后往前(即从k到1)更新。

买卖股票的最佳时期带有一天的冷却期

维护三个一维数组buy, sell,和rest。其中:
buy[i]表示在第i天之前最后一个操作是买,此时的最大收益。
sell[i]表示在第i天之前最后一个操作是卖,此时的最大收益。
rest[i]表示在第i天之前最后一个操作是冷冻期,此时的最大收益。
我们写出递推式为: 

  1. buy[i]  = max(rest[i-1] - price, buy[i-1])   
  2. sell[i] = max(buy[i-1] + price, sell[i-1])  
  3. rest[i] = max(sell[i-1], buy[i-1], rest[i-1])  

上述递推式很好的表示了在买之前有冷冻期,买之前要卖掉之前的股票。一个小技巧是如何保证[buy, rest, buy]的情况不会出现,这是由于buy[i] <= rest[i], 即rest[i] = max(sell[i-1], rest[i-1]),这保证了[buy, rest, buy]不会出现。
另外,由于冷冻期的存在,我们可以得出rest[i] = sell[i-1],这样,我们可以将上面三个递推式精简到两个:

  1. buy[i]  = max(sell[i-2] - price, buy[i-1])   
  2. sell[i] = max(buy[i-1] + price, sell[i-1])  

由于i只依赖于i-1和i-2,所以我们可以在O(1)的空间复杂度完成算法:

    1. pre_buy = buy;【因为buy是上一轮的,此句含义:pre_buy = buy[i-1]】  
    2. buy = max(pre_buy, pre_sell - prices[i]);【此句含义:buy[i]即buy  =  max(buy[i-1]即pre_buy,sell[i-2]【尚未更新的即上一轮的pre_sell】 - prices[i])】  
    3. pre_sell = sell;【因为sell是上一轮的,此句含义:sell[i-1] = sell】  
    4. sell = max(pre_sell, pre_buy + prices[i]);【此句含义:sell[i]即sell   =   max(sell[i-1]即pre_sell,buy[i-1]【已更新为pre_buy了】 + prices[i])】  
  int maxProfit(vector<int>& prices) {
        int n=prices.size();
        if(n<2)  return 0;
        int buy=-prices[0], pre_buy=INT_MIN,sell=0,pre_sell=0;
        
        for(int i=1;i<n;i++){
            pre_buy=buy;
            buy=max(pre_buy, pre_sell-prices[i]);
            pre_sell=sell;
            sell=max(pre_sell, pre_buy+prices[i]);
        }
        return sell;
    }

买卖股票的最佳时期带有K天的冷却期(ali的笔试题)












 

posted @ 2018-05-14 17:42  东风知我欲山行  阅读(270)  评论(0编辑  收藏  举报