1. 原题链接:https://leetcode.com/problems/best-time-to-buy-and-sell-stock/
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.
Example 1:
Input: [7, 1, 5, 3, 6, 4] Output: 5 max. difference = 6-1 = 5 (not 7-1 = 6, as selling price needs to be larger than buying price)
Example 2:
Input: [7, 6, 4, 3, 1] Output: 0 In this case, no transaction is done, i.e. max profit = 0.
这是一道乍一看觉得很简单,但是细想又有点坑的题。思路有好几种,先说最直接的暴力破解思路,遍历数组中的数字p[j],与它前面所有数字p[i]进行相减得出利润,然后求出最大的那个利润即可。换句话说,就是找到每一个上升区间内最大值x和最小值y之间的差s[k],比较得出最大的s = max{ 0<k<n | s[k] }。因为是两层循环,时间复杂度O(n^2),空间复杂度O(1)。
解法1.1public int maxProfit(int[] prices) {
if (prices.length < 2) return 0;
int profit = 0;
for (int i = 0; i < prices.length-1; ++i) {
for (int j = i+1; j < prices.length; ++j) {
profit = Math.max(profit, prices[j] - prices[i]);
}
}
return profit;
}
这么写很好理解,但是提交后会得到超时错误TLE。显然我们必须在时间复杂度上做文章。我们仔细观察一下上面的算法,发现每次计算第i个最大利润的时候,都会计算[0, i-1]之间的最大值与最小值之差。这样做显然做了很多重复的计算,浪费了大量的时间。那我们有没有办法不进行重复计算呢?答案是肯定的。做法就是空间换时间,将[0, i-1]之间的计算结果缓存到一个临时数组里,并且将[0, i-1]的最小值缓存在另一个临时数组里,这样就能避免在i+1的时候,重复计算[0, i]的区间了。时间复杂度被优化到了O(n),空间复杂度因为用到了两个临时数组,所以变成了O(2n)。 解法1.2
public int maxProfit(int[] prices) {
if (prices.length < 2) return 0;
int[] localMin = new int[prices.length];
localMin[0] = prices[0];
int[] profit = new int[prices.length];
for (int i = 1; i < prices.length; ++i) {
profit[i] = Math.max(profit[i-1], prices[i]-localMin[i-1]);
localMin[i] = Math.min(localMin[i-1], prices[i]);
}
return profit[prices.length-1];
}
解法1.2其实就是动态规化的解,将之前计算过的profit值和localMin值(区域最小值)缓存起来,减少一层n循环。
profit[i]: 在第i天所能取得的最大利润
localMin[i]: 在0到i天之间prices的最小值
profit[i] = max{i∈[0, i] | profit[i-1], prices[i] - localMin[i-1]}
localMin[i] = min{i∈[0, i] | localMin[i-1], prices[i]}
一般来说,动态规划能将复杂度降低一个数量级n,同时增加空间复杂度一个数量级n。那有没有办法把动态规划的空间复杂度降低到O(1)呢?答案是:不一定。不是所有的动态规划都能缩减空间复杂度,当dp[i]与dp[k] (k ∈[0, i-m] 并且 m ≪ k) 没有关联性的时候可以优化,否则就不行。例如我们的解法1.2,仔细观察上面的公式后发现:其实在任意一个时间点i,profit[i]只与前一个profit[i-1],当前prices[i]以及前一个localMin[i-1]相关。localMin[i]则只与前一个localMin[i-1]以及当前prices[i]相关。这符合我们刚刚提到的优化条件,m = 2并且m ≪ k。换句话说,我们可以只用两个变量profit和localMin替代profit数组和localMin数组:
解法1.3
public int maxProfit(int[] prices) {
if (prices.length < 2) return 0;
int profit = 0; int min = prices[0];
for (int i = 1; i < prices.length; ++i) {
if (prices[i] < min) {
min = prices[i];
} else if (prices[i] - min > profit) {
profit = prices[i] - min;
}
}
return profit;
}
到此为止,我们得到了这个问题的最优解,O(n)的时间复杂度,以及O(1)的空间复杂度。接下来我会继续讲解II, III, IV, 和cooldown的优化思路。