标签:math lse 左右 htc 压缩 方案 另一个 时间 之间
2020/3/19
Maximum Subarray
Given an integer array nums
, find the contiguous subarray (containing at least one number) which has the largest sum and return its sum.
Example:
Input: [-2,1,-3,4,-1,2,1,-5,4],
Output: 6
Explanation: [4,-1,2,1] has the largest sum = 6.
Follow up:
If you have figured out the O(n) solution, try coding another solution using the divide and conquer approach, which is more subtle.
给定一个整数数组 nums
,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例:
输入: [-2,1,-3,4,-1,2,1,-5,4],
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
进阶:
如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的分治法求解。
分治法解决问题的模板:
算法
当最大子数组有n个数字:
n==1
,返回此元素left_sum
:left到(left+right)/2的最大值right_sum
:(left+right)/2+1到right的最大值cross_sum
是包含左右子数组且含索引(left+right)/2
的最大值class Solution{
public int maxSubArray(int nums[]){
return helper(nums,0,nums.length-1);
}
public int helper(int[] nums,int left,int right){
if(left==right) return nums[left];
int p=(left+right)/2;
int leftSum=helper(nums,left,p);
int rightSum=helper(nums,p+1,right);
int crossSum=crossSum(nums,left,right,p);
return Math.max(Math.max(leftSum,rightSum),crossSum);
}
public int crossSum(int[] nums,int left,int right,int p){
if(left==right) return nums[left];
int leftSubsum=Integer.MIN_VALUE;
int currSum=0;
for(int i=p;i>left-1;--i){
currSum+=nums[i];
leftSubsum=Math.max(leftSubsum,currSum);
}
int rightSubsum=Integer.MIN_VALUE;
currSum=0;
for(int i=p+i;i<right+1;++i){
currSum+=nums[i];
rightSubsum=Math.max(rightSubsum,currSum);
}
return leftSubsum+rightSubsum;
}
}
复杂度分析
解析
与归并排序类似,先切分,再合并结果
关键在于如何切分这些组合才能使每个小组之间不会有重复的组合:
从题目给的案例来看[-2,1,-3,4,-1,2,1,-5,4]
,共有9个元素center=(start+end)/2
这个原则,得到中间元素的索引为4,拆分为三个组合:
[-2,1,-3,4,-1]
及它的子序列(在-1左边的并包含它的为一组)[2,1,-5,4]
及它的子序列(在-1右边不包含它的为一组)以上三个组合的序列没有任何重复的部分,而且一起构成所有子序列的全集,计算出这三个子集合的最大值,然后取其中的最大值,就得到问题的答案
前两个子组合可以用递归解决,第三个跨中心的组合的解决方式:
在计算过程中,累加和比较是关键操作,一个长度为n的数组在递归的每一层都会进行n次操作,分治法的递归层级在logn级别,所以整体复杂度是O(nlogn)
连续子序列的最大和主要由这三部分子区间里元素的最大和得到:
[left,mid]
;[mid+1,right]
[mid,mid+1]
的子区间,即nums[mid]
和num[mid+1]
一定会被选取对它们三者求最大值即可
public int maxSubArray(int[] nums){
return maxSubArrayDivideWithBorder(nums,0,nums.length-1);
}
private int maxSubArrayDivideWithBorder(int[] nums,int start,int end){
if(start==end){
//只有一个元素,即递归的结束情况
return nums[start];
}
//计算中间值
int center=(start+end)/2;
//计算左侧子序列最大值
int leftMax=maxSubArrayDivideWithBorder(nums,start,center);
//计算右侧子序列最大值
int rightMax=maxSubArrayDivideWithBorder(nums,center+1,end);
//下面计算横跨两个子序列的最大值
//计算包含左侧子序列最后一个元素的子序列的最大值
int leftCrossMax=Integer.MIN_VALUE;//初始化
int leftCrossSum=0;
for(int i=center;i<=start;i--){
leftCrossSum += nums[i];
leftCrossMax = Math.max(leftCrossSum,leftCrossMax);
}
//计算包含右侧子序列最左端元素的子序列最大值
int rightCrossMax = nums[center+1];
int rightCrossSum = 0;
for (int i = center + 1; i <= end ; i ++) {
rightCrossSum += nums[i];
rightCrossMax = Math.max(rightCrossSum, rightCrossMax);
}
//计算跨中心的子序列的最大值
int crossMax=leftCrossMax+rightCrossMax;
//比较三者,返回最大值
return Math.max(crossMax,leftMax,rightMax);
}
按照 排列组合的数学算法,9个数组,以第i个数字结尾的串,有i种组合,一共有个45个组合
如果有n个数字,时间复杂度为O(n^2),明显不能接受
首先需要把这个问题分解成最优子问题来解,最主要的思路就是将上面的45进行分解,分解成数量较少的子问题.这里我们一共有9个数字,顺理成章把组合分解成9个小组的组合
[-2]
,最大值-2
[-2,1],[1]
,最大值1
[-2,1,3],[1,3],[3]
,最大值4
如果我们能够得到每一个子组合的最优解,整体的最大值就可以通过比较这9个子组合的最大值得到.我们找到了最优子问题,重叠子问题就需要通过比较每个子问题找出.
从第二个子组合和第三个子组合可以看到,组合3只是在组合2的基础上每一个数组添加了第3个数字,然后增加了一个只有第三个数字的数组[3].这样两个组合之间的关系就出现了.题目不需要关心这个序列怎么生成,只关心最大值之间的关系
[-2,1,3],[1,3]
(最大值1=第二个组合的最大值+第三个数字)[3]
(最大值2=第三个数字)如果第二个序列的最大值大于0,那么最大值1比最大值2大,反之最大值2比较大,这样,我们就通过第二个组合的最大值和第三个数字,得到第三个组合的最大值.因为第二个组合的结果被重复用到了,所以符合重叠子问题的定义.
步骤一:定义状态->定义数组元素的含义
步骤二:状态转移方程->找出数组元素间的关系式
? if(dp[i-1]>=0) dp[i]=dp[i-1]+nums[i];
? if(dp[i-1]<0) dp[i]=nums[i];
步骤三:初始化->找出初始条件
步骤四:状态压缩->优化数组空间
步骤五:选出结果
public class Solution{
public int maxSubArray(int[] nums){
int len=nums.length;
if(len==0){
return 0;
}
int[] dp=new int[len];
dp[0]=nums[0];
for(int i=1;i<len;i++){
if(dp[i-1]>0){
dp[i]=dp[i-1]+nums[i];
}else{
dp[i]=nums[i];
}
}
int res=dp[0];
for(int i=1;i<len;i++){
res=Math.max(res,dp[i]);
}
return res
}
}
public int maxSubArray(int[] nums){
if(nums==null){
return 0;
}
int max=nums[0];
int subMax=nums[0];
for(int i=1;i<nums.length;i++){
if(subMax>0){
subMax=subMax+nums[i];
}else{
subMax=nums[i];
}
max=Math.max(max,subMax);
}
return max;
}
延伸--获取最大序列的起始和结束位置
public int maxSubArrayPosition(int[] nums){
if(nums==null){
return 0;
}
int start = 0;
int end=0;
int subStart=0;
int subEnd=0;
int max=nums[0];
int subMax=nums[0];
for(int i=1;i<nums.length;i++){
if(subMax>0){
//前一个子组合最大值大于0,正增益,更新最后元素的位置
subMax=subMax+nums[i];
subEnd++;
}else{
//前一个子组合最大值小于0,抛弃前面的结果,更新最大值位置
subMax=nums[i];
subStart=i;
subEnd=i;
}
//计算全局最大值,更新位置,将全局最优解的位置更新
if(subMax>max){
max=subMax;
start=subStart;
end=subEnd;
}
}
if(start==end){
System.out.println("["+start+"]");
}else{
System.out.println("["+start++","+end+"]");
}
return max;
}
算法
遍历数组并在每个步骤中更新:
class Solution{
public int maxSubArray(int[] nums){
int n=nums.length;
int currSum=nums[0],maxSum=nums[0];
for(int i=0;i<n;++i){
currSum=Math.max(nums[i],currSum+nums[i]);
maxSum=Math.max(maxSum,currSum);
}
return maxSum;
}
}
复杂度分析
标签:math lse 左右 htc 压缩 方案 另一个 时间 之间
原文地址:https://www.cnblogs.com/ningdeblog/p/12541987.html