想了一个非常复杂的解法,大概方法是先向右查找,如果添加右边一个情况会更好就则继续向右,如果删去左边一个情况会更好,就删去左边一个,但有时候局部情况不等于整体情况,所以会出问题,有的 case 过不了,就非常 sad。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| class Solution { public int maxSubArray(int[] nums) { if(nums.length==1){ return nums[0]; } int left=0; int right=0; int maxSum=nums[0]; int nowSum=nums[0]; while(right<nums.length-1 && left<=right){ if(nums[right+1]>=0){ nowSum+=nums[right+1]; maxSum=nowSum>maxSum?nowSum:maxSum; right++; }else{ if(nums[left]>0 || -nums[left]<=nums[right+1] || left==right){ nowSum+=nums[right+1]; maxSum=nowSum>maxSum?nowSum:maxSum; right++; }else{ nowSum-=nums[left]; maxSum=nowSum>maxSum?nowSum:maxSum; left++; } } System.out.println(left); System.out.println(right); System.out.println(nowSum); System.out.println(maxSum); System.out.println("++++++++"); } while(left<nums.length-1){ nowSum-=nums[left]; maxSum=nowSum>maxSum?nowSum:maxSum; left++; System.out.println(left); System.out.println(right); System.out.println(nowSum); System.out.println(maxSum); System.out.println("++++++++"); } return maxSum; } }
|
看了答案,才理解该怎么解。先寻找局部最大,意思就是如果当前数字加上之前的最大还比现在的数字小,说明可以放弃之前的最大,将当前数字作为最优大了。然后将比较大的局部最大赋值给全局最大。
这个思路的确很有意思,即解决了我之前寻找全局最大可能找不到的问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| class Solution { public int maxSubArray(int[] nums) { if(nums.length==1) return nums[0]; int allMax=nums[0]; int localMax=nums[0]; for(int i=1;i<nums.length;i++){ int add=localMax+nums[i]; localMax=add>nums[i]?add:nums[i]; allMax=allMax>localMax?allMax:localMax; } return allMax; } }
|
只击败了 20.9% 的人,看起来的确效率不高。
搜索了以后发现,这个问题没这么简单,它的最佳线性解法是 Kadane 算法,由 CMU 的 Kadane 教授在 1984 年提出。
算法描述:
- 遍历该数组, 在遍历过程中, 将遍历到的元素依次累加起来, 当累加结果小于或等于 0 时, 从下一个元素开始,重新开始累加。
- 累加过程中, 要用一个变量(max_so_far)记录所获得过的最大值
- 一次遍历之后, 变量 max_so_far 中存储的即为最大子片段的和值。
使用 Wikipedia 中的 Python 代码的含义,书写 Java 代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| class Solution { public int maxSubArray(int[] nums) { int max_ending_here = 0; int max_so_far = Integer.MIN_VALUE;
for(int i = 0; i < nums.length; i++){ if(max_ending_here < 0) max_ending_here = 0; max_ending_here += nums[i]; max_so_far = Math.max(max_so_far, max_ending_here); } return max_so_far; } }
|
用时和前面的代码接近。
以下对代码的理解摘自https://blog.csdn.net/lengxiao1993/article/details/52303492
- 最大子片段是可以为空的, 空片段的和值是 0。 这一点必须要明确, 不然会无法理解算法的正确性, 所以当给定的数列中,求和为负数的时候,例如【-2,1, -3】, 算法会返回最大求和值 0, 因为默认该数组的最大子片段是一个空序列。
- 最大子片段中不可能包含求和值为负的前缀。 例如 【-2, 1,4】 必然不能是最大子数列, 因为去掉值为负的前缀后【-2,1】, 可以得到一个更大的子数列 【4】、
- 所以在遍历过程中,每当累加结果成为一个非正值时, 就应当将下一个元素作为潜在最大子数列的起始元素, 重新开始累加。
- 由于在累加过程中, 出现过的最大值都会被记录, 且每一个可能成为 最大子数列起始元素 的位置, 都会导致新一轮的累加, 这样就保证了答案搜索过程的完备性和正确性。
https://blog.csdn.net/the__apollo/article/details/77367534 从数学归纳法解释了 Kadane 算法。
题目中建议使用分治算法,思路是将数组不断二分,那么包含最大和的区间将分为这三种。
- 区间完全在 A[low,mid-1]
- 区间完全在 A[mid+1,high]
- 区间包含有 A[mid]
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| class Solution { public int maxSubArray(int[] nums) { return divide(nums,0,nums.length-1); }
public int divide(int[] nums, int left, int right){ if(left==right) return nums[left]; if(left+1==right) return Math.max(nums[left]+nums[right],Math.max(nums[left],nums[right])); int mid=(left+right)/2; int lmax=divide(nums,left,mid-1); int rmax=divide(nums,mid+1,right); int mmax=nums[mid]; int temp=mmax; for(int i=mid-1;i>=left;i temp+=nums[i]; mmax=mmax>temp?mmax:temp; } temp=mmax; for(int i=mid+1;i<=right;i++){ temp+=nums[i]; mmax=mmax>temp?mmax:temp; } return Math.max(mmax,Math.max(lmax,rmax)); } }
|