一、题目
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例:
输入: [-2,1,-3,4,-1,2,1,-5,4]
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
进阶:
如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的分治法求解
二、解决
1、暴力破解
思路:
这个没啥特别好说的,就是取数组下标[i, j]的和,全部相加比较后,取最大值返回。具体实现看代码。
代码:
class Solution {
public int maxSubArray(int[] nums) {
int currSum, maxSum=nums[0];
for(int i=0; i<nums.length; i++) {
currSum = 0; /* A[i]到A[j]的子列和*/
for(int j=i; j<nums.length; j++) {
currSum += nums[j];
if(currSum>maxSum) maxSum = currSum; /* 如果刚得到的这个子列和更大,则更新结果*/
}
}
return maxSum;
}
}
时间复杂度:O(
n
2
n^2
n2)
空间复杂度:O(1)
2、分治
思路:
Step1. 选择数组中间元素,最大值有是否包含中间值两种可能。
Step 2.1 若最大值不包含中间值,则结果就在 [left,middle) 、(middle, right]和[left,middle) U (middle, right]三个区间中。
Step 2.2 若最大值包含中间值,则结果在 [left, middle] 、 [middle, right] 和 [left, right] 两个区间中。
Step 3 返回上述结果的最大值。
代码:
class Solution {
public int maxSubArray(int[] nums) {
if(nums.length==0) return 0;
return maxSubArrayHelper(nums, 0, nums.length-1);
}
int maxSubArrayHelper(int nums[], int left, int right) {
if(left==right) return nums[left];
int middle = (left+right)/2;
int leftAns = maxSubArrayHelper(nums, left, middle);
int rightAns = maxSubArrayHelper(nums, middle+1, right);
int leftMax = nums[middle];
int rightMax = nums[middle+1];
int currSum = 0;
for(int i=middle; i>=left; i--) {
currSum += nums[i];
if(currSum > leftMax) leftMax = currSum;
}
currSum = 0;
for(int i=middle+1;i<=right;i++) {
currSum += nums[i];
if(currSum > rightMax) rightMax = currSum;
}
return Math.max(Math.max(leftAns, rightAns),leftMax+rightMax);
}
}
理解: 相较动态规划而言,时间复杂度较高,但分治可以求任意区间[left, right]的最大值。如果我们把中间结果用堆式存储的记忆方式记录下来,可以建成线段树。然后我们可以在O(logn)的时间复杂度内求任意区间内的最大子列和,在大规模数据的应用场景中,可以体现该方法的优势。
时间复杂度: O(nlogn)
空间复杂度: O(logn)
3、动态规划
版本1
思路:
经过总结推导,可以获得最大子列和的推导式。
dp[i]:以nums[i]结尾的最大子列和。
dp[i] = dp[i-1]>0 ? dp[i-1]+nums[i] : nums[i]
简单说,如果之前子列和对后续无正向增益效果,则抛弃之;有,则保留。更具体的解释请参考3,里面有视频讲解。
代码:
class Solution {
public int maxSubArray(int[] nums) {
int dp[] = new int[nums.length]; int maxSum = nums[0]; dp[0] = nums[0];
for (int i = 1; i < nums.length; i++) {
dp[i] = Math.max(dp[i-1] + nums[i] ,nums[i]);
maxSum = Math.max(maxSum, dp[i]);
}
return maxSum;
}
}
时间复杂度: O(n)
空间复杂度: O(n)
版本2
思路:
由于版本1的空间复杂度为O(n),所以可以再优化,最终到达O(1)空间复杂度。
代码:
class Solution {
public int maxSubArray(int[] nums) {
int currMax=nums[0], maxSum=nums[0];
for(int i=1; i<nums.length; i++) {
currMax = Math.max(currMax+nums[i],nums[i]);
maxSum = Math.max(maxSum,currMax);
}
return maxSum;
}
}
时间复杂度: O(n)
空间复杂度: O(1)
三、参考
1、画解算法:53. 最大子序和
2、最大子序和 c++实现四种解法 暴力法、动态规划、贪心法和分治法 图示讲解
3、3 应用实例:最大子列和问题
4、DP solution & some thoughts
5、My Divide and Conquer Solution in Java under instruction of CLRS(O(nlogn))
6、My concise O(n) DP JAVA Solution
7、How to solve “Maximum Subarray” by using the divide and conquer approach ?
8、JAVA O(n)time O(1) space 5 lines of code