53. Maximum Subarray
题意是求最大子序列和
input:[−2,1,−3,4,−1,2,1,−5,4]
ans:6 [4,−1,2,1]
下面给出三种不同时间复杂度的解法。
一. O(N^2)
两个point,一个指向子序列的头,一个指向子序列的尾,两层循环求该子序列的和。
public class Solution {
public int maxSubArray(int[] nums) {
int max = nums[0];
for(int i = 0; i < nums.length; i++) {
int temp = 0;
for(int j = i; j < nums.length; j++) {
temp += nums[j];
max = temp > max ? temp : max;
}
}
return max;
}
}
二. O(N*logN)
步骤如下:
1.取数组的中间元素mid(向下取整),最大子序列无非存在两种情况:
1.1跨越中间元素
最大子序列中一定存在mid和mid+1(由于mid向下取整,mid+1<=right),那么就从mid向左计算左最大值,从mid+1向右计算右最大值。这种情况的结果是左最大值加右最大值。
1.2不跨越中间元素
将原数组分为两个部分:[left, mid], [mid+1, right],对这两个数组再次使用步骤1。
这种方法是分治法(Divide and Conquer),用递归实现,一般取中间值的算法,复杂度都跟logN有关。
public class Solution {
public int maxSubArray(int[] nums) {
return helper(nums, 0, nums.length - 1);
}
private int helper(int[] nums, int left, int right) {
if(left == right) return nums[left];
int mid = left + ((right - left) >> 1);
int leftMax = helper(nums, left, mid);
int rightMax = helper(nums, mid + 1, right);
int tempSum = 0;
int maxL = nums[mid];
int maxR = nums[mid+1];
for(int i = mid; i >= left; i--) {
tempSum += nums[i];
maxL = Math.max(tempSum, maxL);
}
tempSum = 0;
for(int i = mid + 1; i <= right; i++) {
tempSum += nums[i];
maxR = Math.max(tempSum, maxR);
}
return Math.max(Math.max(leftMax, rightMax), maxL + maxR);
}
}
三. O(N)
最大子序列算是动态规划(Dynamic Programming)的一个经典例题。通常我们需要找到一个递推公式。设max(nums, i)是以nums[i]为结尾(该序列中一定包含nums[i],且以nums[i]结尾)的最大子序列和,递推式为:
max(nums, i) = (max(nums, i-1) < 0 ? 0 : max(nums, i-1)) + nums[i];
由于一定要包含nums[i],那么就不必考虑nums[i]是大于0还是小于0。要考虑的是nums[i]前面的序列,如果前面的序列大于0,就要它;如果小于0,就不要它。由于定义了DP数组,空间复杂度为O(N)。
public class Solution {
public int maxSubArray(int[] nums) {
int[] DP = new int[nums.length];
int max = nums[0];
DP[0] = nums[0];
for(int i = 1; i < nums.length; i++) {
DP[i] = (DP[i-1] < 0 ? 0 : DP[i-1]) + nums[i];
max = Math.max(max, DP[i]);
}
return max;
}
}
一般来说,可以通过优化动态规划中的数组来优化空间复杂度。从for循环中的递推式可以看到:
DP[i] = (DP[i-1] < 0 ? 0 : DP[i-1]) + nums[i];
每次循环只需要上一次的DP值,那么我们就不需要定义DP数组了,空间复杂度可以降低到O(1)。
//空间复杂度O(1)
public class Solution {
public int maxSubArray(int[] nums) {
int DP = nums[0];
int max = nums[0];
for(int i = 1; i < nums.length; i++) {
DP = (DP < 0 ? 0 : DP) + nums[i];
max = Math.max(max, DP);
}
return max;
}
}
四. 注意事项
1.程序中类似于max变量的初始化不能为0,因为序列有可能全是负值,0大于负值,这样最后的结果会是0。
2.对于一种类型的题目,最好能知道不同复杂度的解法,面试也从最简单的解法入手,然后一步一步去优化。