算法设计与分析——最大子段和(分治)
一、问题描述
对应的力扣练习:https://leetcode-cn.com/problems/lian-xu-zi-shu-zu-de-zui-da-he-lcof/
Description
给定有n个整数(可能为负整数)组成的序列a1,a2,...,an,求该序列连续的子段和的最大值。 如果该子段的所有元素和是负整数时定义其最大子段和为0。
Input
第一行有一个正整数n(n<1000),后面跟n个整数,绝对值都小于10000。直到文件结束。
Output
输出它的最大子段和。
Sample Input
6 -2 11 -4 13 -5 -2
Sample Output
24
解决该问题有很多方法可以通过暴力、动态规划和分治,这里使用分治的方法来解决该题目。
二、分治策略
用分治法求解这个问题 。
在数组的 center = (right-left)/2+left 位置处分开。形成两个子数组。
那么,最大子段和可能出现在三个位置:
- 可能出现在左子数组
- 可能出现在 右子数组
- 可能出现在 过center的中间某部分元素组成的子数组。
下面考虑 三种情况的计算方法:
第一种情况: 计算 left 到 center 的最大和,记作 leftSum
第二种情况: 计算从 center+1 到 right的最大和,记作 rightSum
第三种情况: 跨边界的和。 以center为中心分别向两边计算和。
a.从 center出发,每次向左边扩张一步,并且记录当前的值S1,如果当前的和比上次的和大,就更新S1,一直向左扩张到位置 Left。
b.从 center+1出发,每次扩张一步,计算当前的和 为S2,如果当前的值比上次的和 大,那么,就更新S2的值,一直向右扩张到位置Right。
c.计算过center的连续值的和,S1+S2的值 Sum。 这个就是跨边界的和。
上面三种情况考虑计算完成后,最后一步就是,比较三个值中的最大值,取最大值就可以了。
时间复杂度
我们在(left+right)/2处,把大问题分成了两个部分的小问题。
在跨边界求和的时候,我们需要计算 从center出发的到 left方向的最大和,每次增加一个元素,通过比较,得到最大的和,时间复杂度为O(N),同样, 从center+1 出发的到 right 方向的最大和,每次增加一个元素,通过比较,得到最大的和,时间复杂度 为 O(N) ,所以,时间复杂度为 O(N)。
伪代码
Java源码
class Solution {
// 分治法
public int maxSubArray(int[] nums) {
return divideConquer(nums,0,nums.length-1);
}
public int divideConquer(int[] nums,int low,int high) {
//递归出口
if (low>=high){
return nums[low];
}
//三种情况,第一种:数组在左区间
//第二种:数组在又区间
//第三种:数组横跨左右区间
//前两种递归求解即可
//第三种情况以中间位置向两周扩散,找到最大值
int mid = low+(high-low)/2;
int left = divideConquer(nums,low,mid-1);
int right = divideConquer(nums,mid+1,high);
int maxMid = nums[mid];
int curMId = nums[mid];
for (int i = mid-1;i>=low;i--){
curMId += nums[i];
if (maxMid<curMId) maxMid = curMId;
}
curMId = maxMid;
for (int i = mid+1;i<=high;i++){
curMId += nums[i];
if (maxMid<curMId) maxMid = curMId;
}
//4.返回三种情况中的最大值
return Math.max(Math.max(left,right),maxMid);
}
}