买卖股票系列 && Subarray
相关题目:
Best Time to Buy and Sell Stock I && II && III (IV 单独开贴)
Maximum Subarray I && II
为什么这些我要总结到一起呢?因为思路基本一致。
题目简略:
买卖股票1: 一次买卖,利润最大。
买卖股票2: N次买卖,利润最大。
买卖股票3: 2次不重叠的买卖,利润最大。
Maximum Subarray: Given an array of integers, find a contiguous subarray which has the largest sum.
Maximum Subarray II: 两次分割, 求最大sum
不难看出,其实买卖股票1跟Maximum subarray是一个题,所以先来说说这个热身题。
Maximum Subarray:
Given an array of integers, find a contiguous subarray which has the largest sum.
Example
Given the array [−2,2,−3,4,−1,2,1,−5,3]
, the contiguous subarray [4,−1,2,1]
has the largest sum = 6
.
Note
The subarray should contain at least one number.
Challenge
Can you do it in time complexity O(n)?
SOLUTION:
最简单的思路,连续子数组有n^2个(开头的位置有n个可能,结尾也有n个可能,一共就是n^2个可能)。两个循环for 头,for尾,可以解决。
再来思考O(n)的方法:
这里已看到Subarray就一定要知道一个概念 prefix sum:前缀和,也就是prefix[i] = sum(A[0]~A[i]). 从prefix sum就可以转化到subarray sum,具体这样实现:
subarray[i, j] = prefix[j] - prefix[i - 1]
既然有这种方法,求最大值也就是说,对于一个固定点来说,找到之前prefix的最小值,就可能找到他们差值的最大值了。
代码:
public class Solution { /** * @param nums: A list of integers * @return: A integer indicate the sum of max subarray */ public int maxSubArray(int[] nums) { if (nums == null || nums.length == 0){ return 0; } int[] prefix = new int[nums.length + 1]; prefix[0] = 0; int min = Integer.MAX_VALUE; int maxSum = Integer.MIN_VALUE; for (int i = 1; i <= nums.length; i++){ prefix[i] = prefix[i - 1] + nums[i - 1]; min = Math.min(min, prefix[i - 1]); maxSum = Math.max(maxSum, prefix[i] - min); } return maxSum; } }
再来看,买卖股票1:
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
Given an example [3,2,3,1,2], return 1
SOLUTION:
思路完全一样,对于类似这种找两个点的情况,一定要先固定一个点,我们选择固定卖出时机,然后我们再找一个之前的(最小)买入时机,也就可以得到最大利润。
但是每次卖出点向后移动的时候,只需要看移动时产生的可能买入点,能不能更新之前维护的(最小)买入时机就可以,不需要重复遍历
code:
public class Solution { /** * @param prices: Given an integer array * @return: Maximum profit */ public int maxProfit(int[] prices) { if (prices == null || prices.length == 0){ return 0; } int min = Integer.MAX_VALUE; // buy timing int sum = 0; for (int i = 1; i < prices.length; i++){ min = Math.min(prices[i - 1], min); sum = Math.max((prices[i] - min), sum); } return sum; } }
Follow Up:
Best Time to Buy and Sell Stock II
N次买卖。
这就更简单了,只要发现第二天涨了,就把之前的买的卖了就可以。
code:
class Solution { /** * @param prices: Given an integer array * @return: Maximum profit */ public int maxProfit(int[] prices) { if (prices == null || prices.length == 0){ return 0; } int profit = 0; for (int i = 1; i < prices.length; i++){ if (prices[i] > prices[i - 1]){ profit = profit + prices[i] - prices[i - 1]; } } return profit; } };
Follow Up:
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.
Example
Given an example [4,4,6,1,1,4,2,5]
, return 6
.
Note
You may not engage in multiple transactions at the same time (ie, you must sell the stock before you buy again).
SOLUTION:
这题的难点是,两次买卖,但是两次买卖不能交叉重叠。
采用扫描线方法:模拟一条扫描线,线的左右,分别做一次买卖,使得这次买卖利润最大。
如何实现这个思路呢? 可以开两个数组纪录答案,一个纪录从左边到右边的一次买卖,另一个纪录从右边到左边的一次买卖。left[i] + right[i]就是同一个扫描线的情况下,两边利润和。
因为从做往右维护一个最小点(min)买入点比较方便,而从右往左则是维护一个最大(max)卖出点比较方便,left[]表示在i处卖了股票, right[]表示在i处买了股票的最大值,所以结果是left[i] + right[i] 而不是left[i] + right[i + 1]。
code:
class Solution { /** * @param prices: Given an integer array * @return: Maximum profit */ public int maxProfit(int[] prices) { if (prices == null || prices.length == 0){ return 0; } int[] left = new int[prices.length]; int[] right = new int[prices.length]; int min = Integer.MAX_VALUE; //固定买入的点(最小点) for (int i = 0; i < prices.length - 1; i++){ min = Math.min(min, prices[i]); left[i + 1] = Math.max((prices[i + 1] - min), left[i]); } int max = Integer.MIN_VALUE; //固定卖出的点(最大点) for (int i = prices.length - 1; i > 0; i--){ max = Math.max(max, prices[i]); right[i - 1] = Math.max((max - prices[i - 1]), right[i]); } int sum = 0; //当天卖出再当天买入产生的最大值 for (int i = 0; i < prices.length; i++){ sum = Math.max(sum, left[i] + right[i]); } return sum; } };
Maximum Subarray II (Maximum Subarray I follow up)
Given an array of integers, find two non-overlapping subarrays which have the largest sum.
The number in each subarray should be contiguous.
Return the largest sum.
Example
For given [1, 3, -1, 2, -1, 2], the two subarrays are [1, 3] and [2, -1, 2] or [1, 3, -1, 2] and [2], they both have the largest sum 7.
Note
The subarray should contain at least one number
Challenge
Can you do it in time complexity O(n) ?
SOLUTION:
这题跟上面实际是一样的,处理时候有一点点区别,就是处理right[i]时候,是从右到左的subarray sum,所以最后sum = left[i] + right[i + 1]
public class Solution { /** * @param nums: A list of integers * @return: An integer denotes the sum of max two non-overlapping subarrays */ public int maxTwoSubArrays(ArrayList<Integer> nums) { int[] right = new int[nums.size()]; int[] left = new int[nums.size()]; int sum = 0; int max = Integer.MIN_VALUE; int minSum = 0; for (int i = 0; i < nums.size(); i++){ sum = sum + nums.get(i); max = Math.max(max, sum - minSum); minSum = Math.min(minSum, sum); left[i] = max; } sum = 0; minSum = 0; max = Integer.MIN_VALUE; for (int i = nums.size() - 1; i >= 0; i--){ sum = sum + nums.get(i); max = Math.max(max, sum - minSum); minSum = Math.min(minSum, sum); right[i] = max; } max = Integer.MIN_VALUE; for (int i = 0; i < nums.size() - 1; i++){ max = Math.max(max, left[i] + right[i + 1]); } return max; } }