Best Time to Buy and Sell Stock系列

I题java

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.数组

Subscribe to see which companies asked this question优化

解答:ui

采用动态规划解法,遍历数组更新当前最小值,并用当前值减去最小值与当前最大利润进行比较,更新最大利润。this

public class Solution {
    public int maxProfit(int[] prices) {
        if (prices == null || prices.length == 0) {
            return 0;
        }
        int min = Integer.MAX_VALUE;
        int profit = 0;
        for (int i : prices) {
            min = Math.min(min, i);
            profit = Math.max(i - min, profit);
        }
        return profit;
    }
}

 

IIspa

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

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

Subscribe to see which companies asked this questionblog

解答:

采用贪心算法,记录相邻两天的差值,大于零则加到利润总量中。(本题知足贪心算法能够获得最优解的特性是重点)

public class Solution {
    public int maxProfit(int[] prices) {
        int profit = 0;
        for (int i = 0; i < prices.length - 1; i++) {
            int pro = prices[i + 1] - prices[i];
            if (pro > 0) {
                profit += pro;
            }
        }
        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]

解答:

设置三个数组,buy sell和rest。

buy[i]表示第i天以buy状态结束时至今能够得到的最大profit;

sell[i]表示第i天以sell状态结束至今能够得到的最大profit;

rest[i]表示第i天以rest状态结束至今能够得到的最大profit。

根据定义可得:

buy[i]  = max(rest[i-1]-price, buy[i-1]) //前者表示在前一天rest的状况下今天购入,后者表示在前一天处于buy状态的状况下保持buy状态(即不操做)
sell[i] = max(buy[i-1]+price, sell[i-1]) //前者表示在前一天处于buy状态的状况下今天卖出,后者表示前一天已经处于sell状态的状况下保持sell状态(即不操做)
rest[i] = max(sell[i-1], buy[i-1], rest[i-1]) //rest表示没有任何操做,因此其值应为前一天处于sell,buy和rest状态中的最大值

上述等式考虑了两个规则,即buy必须在rest以后,sell必须在buy以后,由一式和二式能够获得。但还有一个特殊状况[buy,rest,buy]须要考虑,从上述三式不能明显得出这种特殊状况不存在。

由buy[i] <= rest[i],故rest[i] = max(sell[i-1], rest[i-1]),由此式能够获得[buy,rest,buy]不可能发生。

由rest[i] <= sell[i],故rest[i] = sell[i-1]。

因而上述三个等式能够化为两个:

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

第i天状态只和第i-1天和i-2天有关,因此空间能够由O(n)缩小为O(1)。

具体代码以下:

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;
}

 

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

问题分析:

最初的想法是在prices数组中寻找一个分割点,把数组分割为两部分。而后利用第一个问题的方法去分别寻找两部分的结果,将结果相加,得出的最大结果即为最终的最大利润。代码以下:

public class Solution {
    public int maxProfit(int[] prices) {
        int n = prices.length;
        int rst = 0;
        for (int i = 0; i < n; i++) {
            int temp = helper(prices, 0, i) + helper(prices, i + 1, n - 1);
            if (rst < temp) {
                rst = temp;
            }
        }
        return rst;
    }
    public int helper(int[] prices, int i, int j) {
        if (i > j) return 0;
        int min = Integer.MAX_VALUE;
        int rst = 0;
        for (int k = i; k <= j; k++) {
            min = Math.min(prices[k], min);
            rst = Math.max(prices[k] - min, rst);
        }
        return rst;
    }
}

这段代码在lintcode上经过了全部test case,可是在leetcode上超时了。时间复杂度为O(n^2).

利用数组dp[k][i]来表示以在prices下标i以前(包含i)进行k次交易获得的最大利润。则能够获得递推公式为

dp[k][i] = max(dp[k][i - 1], max(dp[k - 1][j] + prices[i] - prices[j])), 其中j的取值范围为[0, i - 1].

              = max(dp[k][i - 1], prices[i] + max(dp[k -1][j] - prices[j]))

              = max(dp[k][i - 1], prices[i] + max(prev[k - 1])), 在内层循环中只须要每次更新这个值便可。

根据实际意义,dp[0][i]和dp[i][0]均为0. 

具体代码以下:

public class Solution {
    public int maxProfit(int[] prices) {
        if (prices == null || prices.length <= 1) return 0;
        int k = 2;
        int[][] dp = new int[k + 1][prices.length];
        for (int t = 1; t <= k; t++) {
            int tmp = Integer.MIN_VALUE;
            for (int i = 1; i < prices.length; i++) {
                tmp = Math.max(tmp, dp[t - 1][i - 1] - prices[i - 1]);
                dp[t][i] = Math.max(dp[t][i - 1], prices[i] + tmp);
            }
        }
        return dp[k][prices.length - 1];
    }
}

问题优化:

考虑此时可否把二维数组简化为一维数组,发现tmp既与i的上一个状态有关,还与t的上一个状态有关。此时不方便简化。

仔细分析发如今递推公式dp[k][i] = max(dp[k][i - 1], max(dp[k - 1][j] + prices[i] - prices[j]))中j取i时也可行。也就意味着在内部循环式变为

1)tmp = Math.max(tmp, dp[t - 1][i] - prices[i])

2)dp[t][i] = Math.max(dp[t][i - 1], prices[i] + tmp)

此时将数组简化为一维数组dp[i]每次用i - 1的状态更新i的状态便可,具体代码以下:

public class Solution {
    public int maxProfit(int[] prices) {
        if (prices == null || prices.length <= 1) return 0;
        int k = 2;
        int[] dp = new int[prices.length];
        for (int t = 1; t <= k; t++) {
            int tmp = dp[0] - prices[0];
            for (int i = 1; i < prices.length; i++) {
                tmp = Math.max(tmp, dp[i] - prices[i]);
                dp[i] = Math.max(dp[i - 1], prices[i] + tmp);
            }
        }
        return dp[prices.length - 1];
    }
}

 

进一步优化:

考虑问题的继续优化,dp[i]只与dp[i - 1]有关,而且k = 2时外层循环只有两次,因此只须要四个变量来记录变化便可。

此时只须要对prices进行遍历,更新这四个变量便可。具体意义为:

1)buy1:当前购买了第一支股票的最大获利;

2)sell1:当前售出了第一支股票的最大获利;

3)buy2:当前购买了第二支股票的最大获利;

4)sell2:当前售出了第二支股票的最大获利。

public class Solution {
    public int maxProfit(int[] prices) {
        if (prices == null || prices.length <= 1) return 0;
        int buy1 = Integer.MIN_VALUE;
        int buy2 = Integer.MIN_VALUE;
        int sell1 = 0;
        int sell2 = 0;
        for (int i : prices) {
            buy1 = Math.max(buy1, - i);
            sell1 = Math.max(sell1, i + buy1);
            buy2 = Math.max(buy2, sell1 - i);
            sell2 = Math.max(sell2, i + buy2);
        }
        return sell2;
    }
}

 

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.

问题解答:

这里用III中的普适方法会有一个test case出现MLE的问题,缘由是k值过大。在这里对于k值比二分之一数组长度大时,问题能够简化为问题II。采用quickSolve方法解决此时的问题,代码以下:

public class Solution {
    public int maxProfit(int k, int[] prices) {
        if (prices == null || prices.length <= 1) return 0;
        if (k >= prices.length / 2) return quickSolve(prices);
        int[][] dp = new int[k + 1][prices.length];
        for (int t = 1; t <= k; t++) {
            int tmp = dp[t - 1][0] - prices[0];
            for (int i = 1; i < prices.length; i++) {
                dp[t][i] = Math.max(dp[t][i - 1], prices[i] + tmp);
                tmp = Math.max(tmp, dp[t - 1][i] - prices[i]);
            }
        }
        return dp[k][prices.length - 1];
    }
    public int quickSolve(int[] prices) {
        int rst = 0;
        for (int i = 1; i < prices.length; i++) {
            if (prices[i] > prices[i - 1]) {
                rst += prices[i] - prices[i - 1];
            }
        }
        return rst;
    }
}
相关文章
相关标签/搜索