|
| 1 | +# 题目描述(简单难度) |
| 2 | + |
| 3 | + |
| 4 | + |
| 5 | +给一个数组,看作每天股票的价格,然后某一天买入,某一天卖出,最大收益可以是多少。可以不操作,收入就是 `0`。 |
| 6 | + |
| 7 | +# 解法一 暴力破解 |
| 8 | + |
| 9 | +先写个暴力的,看看对题目的理解对不对。用两个循环,外层循环表示买入时候的价格,内层循环表示卖出时候的价格,遍历所有的情况,期间更新最大的收益。 |
| 10 | + |
| 11 | +```java |
| 12 | +public int maxProfit(int[] prices) { |
| 13 | + int maxProfit = 0; |
| 14 | + for (int i = 0; i < prices.length; i++) { |
| 15 | + for (int j = i + 1; j < prices.length; j++) { |
| 16 | + maxProfit = Math.max(maxProfit, prices[j] - prices[i]); |
| 17 | + } |
| 18 | + } |
| 19 | + return maxProfit; |
| 20 | +} |
| 21 | +``` |
| 22 | + |
| 23 | +# 解法二 双指针 |
| 24 | + |
| 25 | +这种数组优化,经常就是考虑双指针的方法,从而使得两层循环变成一层。思考一下怎么定义指针的含义。 |
| 26 | + |
| 27 | +```java |
| 28 | +用两个指针, buy 表示第几天买入,sell 表示第几天卖出 |
| 29 | +开始 buy,sell 都指向 0,表示不操作 |
| 30 | +3 6 7 2 9 |
| 31 | +^ |
| 32 | +b |
| 33 | +^ |
| 34 | +s |
| 35 | + |
| 36 | +sell 后移表示这天卖出,计算收益是 6 - 3 = 3 |
| 37 | +3 6 7 2 9 |
| 38 | +^ ^ |
| 39 | +b s |
| 40 | + |
| 41 | + |
| 42 | +sell 后移表示这天卖出,计算收益是 7 - 3 = 4 |
| 43 | +3 6 7 2 9 |
| 44 | +^ ^ |
| 45 | +b s |
| 46 | + |
| 47 | +sell 后移表示这天卖出,计算收益是 2 - 3 = -1 |
| 48 | +3 6 7 2 9 12 |
| 49 | +^ ^ |
| 50 | +b s |
| 51 | + |
| 52 | +此外,如上图,当前 sell 指向的价格小于了我们买入的价格,所以我们要把 buy 指向当前 sell 才会获得更大的收益 |
| 53 | +原因很简单,收益的价格等于 prices[sell] - prices[buy],buy 指向 sell 会使得减数更小, |
| 54 | +所以肯定要选更小的 buy |
| 55 | +3 6 7 2 9 12 |
| 56 | + ^ |
| 57 | + s |
| 58 | + ^ |
| 59 | + b |
| 60 | + |
| 61 | + |
| 62 | +sell 后移表示这天卖出,计算收益是 9 - 2 = 7 |
| 63 | +这里也可以看出来减数从之前的 3 变成了 2,所以收益会更大 |
| 64 | +3 6 7 2 9 12 |
| 65 | + ^ ^ |
| 66 | + b s |
| 67 | + |
| 68 | +sell 后移表示这天卖出,计算收益是 12 - 2 = 10 |
| 69 | +3 6 7 2 9 12 |
| 70 | + ^ ^ |
| 71 | + b s |
| 72 | + |
| 73 | +然后在这些价格里选最大的就可以了。 |
| 74 | +``` |
| 75 | + |
| 76 | +代码的话就很好写了。 |
| 77 | + |
| 78 | +```java |
| 79 | +public int maxProfit(int[] prices) { |
| 80 | + int maxProfit = 0; |
| 81 | + int buy = 0; |
| 82 | + int sell = 0; |
| 83 | + for (; sell < prices.length; sell++) { |
| 84 | + //当前价格更小了,更新 buy |
| 85 | + if (prices[sell] < prices[buy]) { |
| 86 | + buy = sell; |
| 87 | + } else { |
| 88 | + maxProfit = Math.max(maxProfit, prices[sell] - prices[buy]); |
| 89 | + |
| 90 | + } |
| 91 | + } |
| 92 | + return maxProfit; |
| 93 | +} |
| 94 | +``` |
| 95 | + |
| 96 | +# 解法三 |
| 97 | + |
| 98 | +参考 <a href = "<https://leetcode.com/problems/best-time-to-buy-and-sell-stock/discuss/39038/Kadane's-Algorithm-Since-no-one-has-mentioned-about-this-so-far-%3A)-(In-case-if-interviewer-twists-the-input)>">这里</a> ,一个很新的角度。 |
| 99 | + |
| 100 | +先回忆一下 [53 题](<https://leetcode.wang/leetCode-53-Maximum-Subarray.html>),求子序列最大的和。 |
| 101 | + |
| 102 | + |
| 103 | + |
| 104 | +当时的解法二,用动态规划, |
| 105 | + |
| 106 | +用一个一维数组 `dp [ i ]` 表示以下标 `i` 结尾的子数组的元素的最大的和,也就是这个子数组最后一个元素是下边为 `i` 的元素,并且这个子数组是所有以 `i `结尾的子数组中,和最大的。 |
| 107 | + |
| 108 | +这样的话就有两种情况, |
| 109 | + |
| 110 | +- 如果 `dp [ i - 1 ] < 0`,那么 `dp [ i ] = nums [ i ]`。 |
| 111 | +- 如果 `dp [ i - 1 ] >= 0`,那么 `dp [ i ] = dp [ i - 1 ] + nums [ i ]`。 |
| 112 | + |
| 113 | +直接放一下最后经过优化后的代码,具体的可以过去 [看一下](<https://leetcode.wang/leetCode-53-Maximum-Subarray.html>)。 |
| 114 | + |
| 115 | +```java |
| 116 | +public int maxSubArray(int[] nums) { |
| 117 | + int n = nums.length; |
| 118 | + int dp = nums[0]; |
| 119 | + int max = nums[0]; |
| 120 | + for (int i = 1; i < n; i++) { |
| 121 | + dp= Math.max(dp + nums[i],nums[i]); |
| 122 | + max = Math.max(max, dp); |
| 123 | + } |
| 124 | + return max; |
| 125 | +} |
| 126 | +``` |
| 127 | + |
| 128 | +而对于这道题我们可以转换成上边的问题。 |
| 129 | + |
| 130 | +对于数组 ` 1 6 2 8`,代表股票每天的价格。 |
| 131 | + |
| 132 | +定义一下转换规则,当前天的价格减去前一天的价格,第一天由于没有前一天,规定为 `0`,用来代表不操作。 |
| 133 | + |
| 134 | +数组就转换为 `0 6-1 2-6 8-2`,也就是 `0 5 -4 6`。现在的数组的含义就变成了股票相对于前一天的变化了。 |
| 135 | + |
| 136 | +现在我们只需要找出连续的和最大是多少就可以了,也就是变成了 `53` 题。 |
| 137 | + |
| 138 | +连续的和比如对应第 3 到 第 6 天加起来的和,那对应的买入卖出其实就是第 `2` 天买入,第 `6` 天卖出。 |
| 139 | + |
| 140 | +换句话讲,买入卖出和连续的和形成了互相映射,所以问题转换成功。 |
| 141 | + |
| 142 | +代码在上边的基础上改一下就可以了。 |
| 143 | + |
| 144 | +```java |
| 145 | +public int maxProfit(int[] prices) { |
| 146 | + int n = prices.length; |
| 147 | + int dp = 0; |
| 148 | + int max = 0; |
| 149 | + for (int i = 1; i < n; i++) { |
| 150 | + int num = prices[i] - prices[i - 1]; |
| 151 | + dp = Math.max(dp + num, num); |
| 152 | + max = Math.max(max, dp); |
| 153 | + } |
| 154 | + return max; |
| 155 | +} |
| 156 | +``` |
| 157 | + |
| 158 | +而这个算法其实叫做 `Kadane` 算法,如果序列中含有负数,并且可以不选择任何一个数,那么最小的和也肯定是 `0`,也就是上边的情况,这也是把我们把第一天的浮动当作是 `0` 的原因。所以 `max `初始化成了 `0`。 |
| 159 | + |
| 160 | +更多`Kadane` 算法的介绍可以参考 [维基百科](<https://zh.wikipedia.org/wiki/%E6%9C%80%E5%A4%A7%E5%AD%90%E6%95%B0%E5%88%97%E9%97%AE%E9%A2%98>)。 |
| 161 | + |
| 162 | +# 总 |
| 163 | + |
| 164 | +这道题虽然是比较简单的,但是双指针的用法还是经常见的。另外解法三对问题的转换是真的佩服了。 |
0 commit comments