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;
    }
}
相關文章
相關標籤/搜索