Best Time to Buy and Sell Stock 股票交易最大收益

诸葛品
2023-12-01

Best Time to Buy and Sell Stock I

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天的价格。 如果只允许进行一次交易,也就是说只允许买一支股票并卖掉,求最大的收益。

  • 贪心法
    分析:从前向后遍历数组,只要当天的价格高于前一天的价格,就算入收益。

  • 性能:O(n)时间,O(1)空间。

public class Solution{
    public int maxProfit(int[] prices){
        int maxProfit = 0;
        int curMin = prices[0];

        if (prices.length < 2)
            return 0;

        for (int i = 0; i < prices.length; i++)
        {
            curMin = Math.min(curMin, prices[i]);
            maxProfit = Math.max(maxProfit, prices[i]-curMin);
        }

        return maxProfit;
    }
}

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).

  • 题意:用一个数组表示股票每天的价格,数组的第i个数表示股票在第i天的价格。交易次数不限,但一次只能交易一支股票,也就是说手上最多只能持有一支股票,求最大收益。

  • 双指针法
    分析:动态规划法。从前向后遍历数组,记录当前出现过的最低价格,作为买入价格,并计算以当天价格出售的收益,作为可能的最大收益,整个遍历过程中,出现过的最大收益就是所求。

  • 性能:O(n)时间,O(1)空间。

public class Solution
{
    public int maxProfit(int[] prices){
        int maxProfit = 0;

        if (prices.length < 2)
            return 0;

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

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天为分界线,计算第i天之前进行一次交易的最大收益preProfit[i],和第i天之后进行一次交易的最大收益postProfit[i],最后遍历一遍,max{preProfit[i] + postProfit[i]} (0≤i≤n-1)就是最大收益。第i天之前和第i天之后进行一次的最大收益求法同Best Time to Buy and Sell Stock I。

  • 性能:O(n)时间,O(n)空间。

public class Solution
{
    public int maxProfit(int[] prices){
        if (prices.length<2)
            return 0;

        int n = prices.length;
        int maxProfit = 0;
        int preProfit = new int[n];
        int posProfit = new int[n];
        int curPreMin = prices[0];
        int curPosMax = prices[n-1];

        for (int i = 1; i < n; i++)
        {
            curPreMin = Math.min(curPreMin, prices[i]);
            preProfit[i] = Math.max(preProfit[i], prices[i]-curPreMin);
        }

        for (int i = n-2; i >= 0; i--)
        {
            curPosMax = Math.max(curPosMax, prices[i]);
            preProfit[i] = Math.max(posProfit[i+1], curPosMax-prices[i]);
        }
        for (int i = 0; i < n; i++)
        {
            maxProfit = Math.max(maxProfit, preProfit[i] + posProfit[i]);
        }

        return maxProfit;
    }
};
  • 滚动扫描法

  • 分析:其实我们并不需要知道每个时间点买卖第一第二笔股票收益的全部信息,我们只要知道前一个时间点买卖第一第二笔股票的最大收益信息,就可以直到当前最大的收益信息了,这样可以为我们省去额外空间。这里我们遍历prices数组的时候,维护四个变量:release2是在该价格点卖出第二笔股票后手里剩的钱,等于上一轮买入第二笔股票后手里剩的钱加上卖出当前股票价格的钱,或者上一轮卖出第二笔股票后手里剩的钱两者中较大的。hold2是在该价格点买入第二笔股票后手里剩的钱,等于上一轮卖出第一笔股票后手里剩的钱减去买入当前股票价格的钱,或者上一轮买入第二笔股票后手里剩的钱两者中较大的。release1是在该价格点卖出第一笔股票后手里剩的钱,等于上一轮买入第一笔股票后手里剩的钱加上卖出当前股票价格的钱,或者上一轮卖出第一笔股票后手里剩的钱两者中较大的。hold1是在该价格点买入第一笔股票后手里剩的钱,等于初始资金减去买入当前股票价格的钱或者初始资金(不买)中较大的。这里计算顺序按照release2 -> hold2 -> release1 -> hold1,因为卖是要后于买的,而第二次交易也是后于第一次交易的,通过这个顺序我们能用这些变量自身来记录上次的值。相当于release2的时间点要先于hold1四个点。

Prices 3 1 2 8 3 1 9 6
release2 0 0 1 7 7 7 1 1
hold2 -3 -1 -1 -1 4 6 1 1
release1 0 0 1 7 7 7 1 1
hold1 -3 -1 -1 -1 -1 -1 3 3

  • 性能:O(n)时间,O(1)空间。
public class Solution{
    public int maxProfit(int []prices){
        int hold1 = Integer.MIN_VALUE, hold2 = Integer.MIN_VALUE;
        int release1 =0, release2 = 0;

        for (int i = 0; i < prices.length; i++)
        {
            release2 = Math.max(release2, hold2 + prices[i]);
            hold2 = Math.max(hold2, release1 - prices[i]);
            release1 = Math.max(release1, hold1 + prices[i]);
            hold1 = Math.max(hold1, -prices[i]);
        }

        return release2;
    }
}

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).
  • 题意:用一个数组表示股票每天的价格,数组的第i个数表示股票在第i天的价格。最多交易k次,手上最多只能持有一支股票,求最大收益。

  • 动态规划法

  • 分析:传统的动态规划我们会这样想,到第i天时进行j次交易的最大收益,要么等于到第i-1天时进行j次交易的最大收益(第i天价格低于第i-1天的价格),要么等于到第i-1天时进行j-1次交易,然后第i天进行一次交易(第i天价格高于第i-1天价格时)。于是得到动规方程如下(其中diff = prices[i] – prices[i – 1]):

    profit[i][j] = max(profit[i – 1][j], profit[i – 1][j – 1] + diff)
    看起来很有道理,但其实不对,为什么不对呢?因为diff是第i天和第i-1天的差额收益,如果第i-1天当天本身也有交易呢(也就是说第i-1天刚卖出了股票,然后又买入等到第i天再卖出),那么这两次交易就可以合为一次交易,这样profit[i – 1][j – 1] + diff实际上只进行了j-1次交易,而不是最多可以的j次,这样得到的最大收益就小了。

    那么怎样计算第i天进行交易的情况的最大收益,才会避免少计算一次交易呢?我们用一个局部最优解和全局最有解表示到第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] , local[i – 1][j] + diff)
    global[i][j] = max(global[i – 1][j], local[i][j])
    local[i][j]和global[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大于天数时,其实就退化成 Best Time to Buy and Sell Stock II 了。就不能用动规来做了,为什么?(请思考) 另外,Best Time to Buy and Sell Stock III 就是本题k=2的情况,所以说IV是II和III的综合。

  • 代码:时间O(nk),空间O(nk)。

public class Solution {
    public int maxProfit(int k, int[] prices) {
        if(prices.length < 2) 
            return 0;

        int n = prices.length;
        //用II的解法优化k > prices.length / 2的情况
        if(k > n / 2){
            int maxProfit = 0;
            for(int i = 1; i < n; i++){
                if(prices[i]>prices[i-1]) sum += prices[i] - prices[i-1];
            }
            return maxProfit;
        }
        //初始化全局变量和局部变量
        int[][] global = new int[prices.length][k+1];
        int[][] local = new int[prices.length][k+1];
        for(int i = 1; i < n; i++){
            int diff = prices[i] - prices[i-1];
            for(int j = 1; j < k + 1; j++){
                //更新局部变量
                local[i][j] = Math.max(global[i-1][j-1], local[i-1][j]+diff);
                //更新全局变量
                global[i][j] = Math.max(global[i-1][j], local[i][j]);
            }
        }
        return global[n - 1][k];
    }
}
  • 改进的动态规划

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

  • 性能:时间O(nk),空间O(k)。

public class Solution {
    public int maxProfit(int k, int[] prices) {
        if (prices.length < 2) return 0;
        if (k >= prices.length) return maxProfit2(prices);

        int[] local = new int[k + 1];
        int[] global = new int[k + 1];

        for (int i = 1; i < prices.length ; i++) {
            int diff = prices[i] - prices[i - 1];

            for (int j = k; j > 0; j--) {
                local[j] = Math.max(global[j - 1], local[j] + diff);
                global[j] = Math.max(global[j], local[j]);
            }
        }

        return global[k];
    }


    public int maxProfit2(int[] prices) {
        int maxProfit = 0;

        for (int i = 1; i < prices.length; i++) {
            if (prices[i] > prices[i - 1]) {
                maxProfit += prices[i] - prices[i - 1];
            }
        }

        return maxProfit;
    }
}
  • 滚动扫描法

  • 性能:时间 O(N) 空间 O(k)

  • 分析:这个解法和III中滚动扫描法是一样的,区别在于III我们用了固定的四个变量来记录两次交易,而IV中我们需要2k个变量来记录k次交易。

  • 注意:数组长度要初始为k+1,方便我们处理第一笔交易的情况。

public class Solution {
    public int maxProfit(int k, int[] prices) {
        //用II的解法优化k > prices.length / 2的情况
        if(k > prices.length / 2){
            int sum = 0;
            for(int i = 1; i < prices.length; i++){
                if(prices[i]>prices[i-1]) sum += prices[i] - prices[i-1];
            }
            return sum;
        }
        //初始化买卖股票后剩余金钱的数组
        int[] release = new int[k+1];
        int[] hold = new int[k+1];
        for(int i = 0; i < k+1; i++){
            hold[i]=Integer.MIN_VALUE;
        }
        for(int i = 0; i < prices.length; i++){
            for(int j = 1; j < k+1; j++){
                //卖出第j笔交易,所剩余的钱
                release[j] = Math.max(release[j], hold[j]+prices[i]);
                //买入第j笔交易,所剩余的钱
                hold[j] = Math.max(hold[j], release[j-1]-prices[i]);
            }
        }
        return release[k];
    }
}
 类似资料: