public class Solution {
/**
* @param nums: a list of integers
* @return: A integer indicate the sum of minimum subarray
*/
public int minSubArray(ArrayList<Integer> nums) {
// write your code
int max = 0, min = Integer.MAX_VALUE;
int prefixSum = 0;
for(int i = 0; i < nums.size(); i++){
prefixSum += nums.get(i);
min = Math.min(min, prefixSum - max);
max = Math.max(max, prefixSum);
}
return min;
}
}
可以看到,在 kadane's algorithm 中,我们保存的这个 prevMin 始终是连续的,起点不一定是哪,但是终点一定是 current,是一个 local 最优解。我们每次迭代,用 local 最优解去更新 global 最优解。
public class Solution {
/**
* @param nums: a list of integers
* @return: A integer indicate the sum of minimum subarray
*/
public int minSubArray(ArrayList<Integer> nums) {
// write your code
int prevMin = 0;
int cur = nums.get(0);
int minRst = nums.get(0);
for(int i = 1; i < nums.size(); i++){
cur = Math.min(nums.get(i), prevMin + nums.get(i));
minRst = Math.min(minRst, cur);
prevMin = cur;
}
return minRst;
}
}
用区间和的思路:
public class Solution {
public int maxSubArray(int[] nums) {
if(nums == null || nums.length == 0) return 0;
int max = Integer.MIN_VALUE, min = 0;
int prefixSum = 0;
for(int i = 0; i < nums.length; i++){
prefixSum += nums[i];
max = Math.max(max, prefixSum - min);
min = Math.min(min, prefixSum);
}
return max;
}
}
用 Kadane's Algorithm:
public class Solution {
public int maxSubArray(int[] nums) {
if(nums == null || nums.length == 0) return 0;
int cur = nums[0];
int max = nums[0];
for(int i = 1; i < nums.length; i++){
cur = Math.max(nums[i], cur + nums[i]);
max = Math.max(max, cur);
}
return max;
}
}
L 家面经题。
相比较 prefix sum 的问题,操作改成乘法之后有几个不同的地方需要处理:
负号。遇到负号时,原本的 min / max 会直接反转。最优解可能是有一系列正数相乘而来,但也可能是一系列正数中带着偶数个数的负数。
保存以当前元素截止的 min / max subarray (保证连续性),遇到负数时调换相乘。
加法操作遇到 0 是无所谓的,乘法操作却会导致直接切断 prefix product.
每个位置的 max / min subarray 同时考虑当前元素,正确处理 0 之余保证连续性。
其实这个做法与其说像 prefix product ,不如说更像 Kadane's Algorithm. 可以看到这个算法可以有效免疫各种切断的情况,只维护到当前位置结尾的 max / min subarray 结果就可以了。
在这里面,min/max 是连续的,local 以 i 结尾的; rst 是 global 非连续的。
前缀积在这题上没用,所有的都是 localMin / localMax,迭代更新。
不能用 val = 1 来做初始化,要用 nums[0];
nums[i] 为负时,要多开个临时变量,不然会覆盖掉下一行需要的值
public class Solution {
public int maxProduct(int[] nums) {
int max = nums[0], min = nums[0];
int rst = nums[0];
for(int i = 1; i < nums.length; i++){
if(nums[i] > 0){
max = Math.max(nums[i], max * nums[i]);
min = Math.min(nums[i], min * nums[i]);
} else {
int oldMax = max;
max = Math.max(nums[i], min * nums[i]);
min = Math.min(nums[i], oldMax * nums[i]);
}
rst = Math.max(rst, max);
}
return rst;
}
}
Subarray I 只是开胃菜的话,这道题就开始比较认真考察如何利用 prefix sum 做 DP 解决 subarray sum 类问题了。
这次光用 local 变量更新然后返回 global 最优还不够,我们需要保存对于每一个子问题的 global 最优(可能并不以当前位置结尾),用于后面的左右两边全局查询。
我们需要利用 prefix sum 数组,定义两个 dp[]
left[i] 代表从最左边到 i 位置所能取得的最大 subarray sum;
right[i] 代表从最右边到 i 位置所能取得的最大 subarray sum;
两个数组都是 global 解。
每次迭代的变量 minSum 和 prefixSum 是相对于每个位置的 local 解。
这样对于每一个位置 i ,我们都可以用 left[] 和 right[] 的子数组把最优解拼接出来。
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) {
// write your code
if(nums == null || nums.size() == 0) return 0;
int n = nums.size();
// Maximum subarray value from left/right with n elements
int[] left = new int[n];
int[] right = new int[n];
int prefixSum = 0;
int minSum = 0;
int max = Integer.MIN_VALUE;
for(int i = 0; i < n; i++){
prefixSum += nums.get(i);
max = Math.max(max, prefixSum - minSum);
minSum = Math.min(minSum, prefixSum);
left[i] = max;
}
prefixSum = 0;
minSum = 0;
max = Integer.MIN_VALUE;
for(int i = n - 1; i >= 0; i--){
prefixSum += nums.get(i);
max = Math.max(max, prefixSum - minSum);
minSum = Math.min(minSum, prefixSum);
right[i] = max;
}
int rst = Integer.MIN_VALUE;
for(int i = 0; i < n - 1; i++){
rst = Math.max(rst, left[i] + right[i + 1]);
}
return rst;
}
}
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) {
// write your code
if(nums == null || nums.size() == 0) return 0;
int n = nums.size();
// Maximum subarray value from left/right with n elements
int[] left = new int[n];
int[] right = new int[n];
int prevMax = nums.get(0);
int max = nums.get(0);
left[0] = nums.get(0);
for(int i = 1; i < n; i++){
prevMax = Math.max(nums.get(i), nums.get(i) + prevMax);
max = Math.max(max, prevMax);
left[i] = max;
}
prevMax = nums.get(n - 1);
max = nums.get(n - 1);
right[n - 1] = nums.get(n - 1);
for(int i = n - 2; i >= 0; i--){
prevMax = Math.max(nums.get(i), nums.get(i) + prevMax);
max = Math.max(max, prevMax);
right[i] = max;
}
int rst = Integer.MIN_VALUE;
for(int i = 0; i < n - 1; i++){
rst = Math.max(rst, left[i] + right[i + 1]);
}
return rst;
}
}
optimal substructure 确定之后,最重要的是要思考循环的实现顺序:
是从 切分数 k 开始循环,还是从数组位置 i 开始循环?
首先思考这个性质:对于切割数为 j 的数组,如果其 size = i < j ,是无法得出正确结果的。换句话说,每次循环真正的正确起点,一定得至少是从 i = j 开始, i 受限于 j.
那么顺着这个思路,如果以 i 作为外循环,内循环 j 一定是在 [0, i] 区间内,并且 j <= k. 因此内循环需要两个条件:
j <= k;
j <= i;
对于任意指定 j ,需要做一个类似于我们之前计算 subarray I & II 时所需要的初始化,确保即使当前位置为负,作为以当前位置结尾的 localMax 依然选中此元素。因此先做一个 localMax[j - 1][j] = Integer.MIN_VALUE;
public class Solution {
/**
* @param nums: A list of integers
* @param k: An integer denote to find k non-overlapping subarrays
* @return: An integer denote the sum of max k non-overlapping subarrays
*/
public int maxSubArray(int[] nums, int k) {
// write your code here
if(nums == null || nums.length == 0) return 0;
int n = nums.length;
int[][] localMax = new int[n + 1][k + 1];
int[][] globalMax = new int[n + 1][k + 1];
for(int j = 1; j <= k; j++){
localMax[j - 1][j] = Integer.MIN_VALUE;
for(int i = j; i <= n; i++){
localMax[i][j] = Math.max(localMax[i - 1][j], globalMax[i - 1][j - 1])
+ nums[i - 1];
if(i == j) globalMax[i][j] = localMax[i][j];
else globalMax[i][j] = Math.max(localMax[i][j], globalMax[i - 1][j]);
}
}
return globalMax[n][k];
}
}
这题和之前求 maximum non-overlap subarrays 的思路基本一致,只不过要求的数组变成了四个:leftMax, leftMin, rightMax 和 rightMin.
于是这题变成了一个非常适合考察计算各种 local / global prefix sum 数组模板熟练度的问题。
public class Solution {
/**
* @param nums: A list of integers
* @return: An integer indicate the value of maximum difference between two
* Subarrays
*/
public int maxDiffSubArrays(int[] nums) {
// write your code here
// write your code
int n = nums.length;
int[] leftMax = new int[n];
int[] leftMin = new int[n];
int[] rightMax = new int[n];
int[] rightMin = new int[n];
int prefixSum = 0;
int localMin = 0;
int globalMax = Integer.MIN_VALUE;
for(int i = 0; i < n; i++){
prefixSum += nums[i];
globalMax = Math.max(globalMax, prefixSum - localMin);
localMin = Math.min(localMin, prefixSum);
leftMax[i] = globalMax;
}
prefixSum = 0;
int localMax = 0;
int globalMin = Integer.MAX_VALUE;
for(int i = 0; i < n; i++){
prefixSum += nums[i];
globalMin = Math.min(globalMin, prefixSum - localMax);
localMax = Math.max(localMax, prefixSum);
leftMin[i] = globalMin;
}
prefixSum = 0;
localMin = 0;
globalMax = Integer.MIN_VALUE;
for(int i = n - 1; i >= 0; i--){
prefixSum += nums[i];
globalMax = Math.max(globalMax, prefixSum - localMin);
localMin = Math.min(localMin, prefixSum);
rightMax[i] = globalMax;
}
prefixSum = 0;
localMax = 0;
globalMin = Integer.MAX_VALUE;
for(int i = n - 1; i >= 0; i--){
prefixSum += nums[i];
globalMin = Math.min(globalMin, prefixSum - localMax);
localMax = Math.max(localMax, prefixSum);
rightMin[i] = globalMin;
}
int rst = Integer.MIN_VALUE;
for(int i = 0; i < n - 1; i++){
int ans = Math.max(Math.abs(leftMax[i] - rightMin[i + 1]),
Math.abs(leftMin[i] - rightMax[i + 1]));
rst = Math.max(rst, ans);
}
return rst;
}
}
很简单的问题,就当练手了。
public class Solution {
public int maxProfit(int[] prices) {
if(prices == null || prices.length == 0) return 0;
int min = prices[0];
int maxProfit = 0;
for(int i = 1; i < prices.length; i++){
maxProfit = Math.max(maxProfit, prices[i] - min);
min = Math.min(min, prices[i]);
}
return maxProfit;
}
}