算法分析:分治法
给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。**
子数组 是数组中的一个连续部分。
示例 1:
输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。
示例 2:
输入:nums = [1]
输出:1
示例 3:
输入:nums = [5,4,-1,7,8]
输出:23
提示:
1 <= nums.length <= 105
-104 <= nums[i] <= 104
进阶:如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的 分治法 求解。
来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/maximum-subarray
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
提示:在这里我们只对分治法进行描述,本体可以使用动态规划法完成
我们定义一个操作 getInfo(a, l, r) 表示查询 a 序列 [l,r] 区间内的最大子段和,那么最终我们要求的答案就是 getInfo(nums, 0, nums.length() - 1)。
如何分治实现这个操作呢?
对于一个区间 [l,r],我们取 m = (l+r)/2,对区间 [l,m]和 [m+1,r]分治求解。当递归逐层深入直到区间长度缩小为1的时候,递归「开始回升」。
public static MaxSubArray getInfo(int[] a, int l, int r) {
if (l == r) {// 递归出口
return new MaxSubArray(a[l], a[l], a[l], a[l]);
}
// 递归函数
int m = (l + r) / 2;
MaxSubArray lSub = getInfo(a, l, m);// 左子区间
MaxSubArray rSub = getInfo(a, m + 1, r);// 右子区间
return pushUp(lSub, rSub);
}
这个时候我们考虑如何通过 [l,m]区间的信息和 [m+1,r]区间的信息合并成区间 [l,r]的信息。核心点就在于:
显然,在区间[l,r]中,我们需要汇集以下信息:
寻找边界条件(递归出口):不难发现,当序列a长度为1时,mSum=lSum=rSum=iSum=a[i]
当序列a长度大于1时,在区间 [l,r] 中:
提示:[l,m]是区间[l,r]的左子区间,[m+1,r]是区间[l,r]的右子区间
iSum =「左子区间」的 iSum 加上「右子区间」的 iSu
iSum = l.iSum+r.iSum
lSum = 「左子区间」的 lSum 或者 lSum =「左子区间」的 iSum +「右子区间」的 lSum
lSum = Math.max(l.lSum, l.iSum + r.lSum)
rSum =「右子区间」的 rSum 或者 rSum = 右子区间」的 iSum 加上「左子区间」的 rSum
lSum = Math.max(l.lSum, l.iSum + r.lSum)
最后,我们可以考虑 mSum 对应的区间是否跨越 m:
它可能不跨越 m,也就是说 mSum 可能是「左子区间」的 mSum 和 「右子区间」的 mSum 中的一个;
它也可能跨越 m,可能是「左子区间」的 rSum 和 「右子区间」的 lSum 求和。
mSum = Math.max(Math.max(l.mSum,r.mSum),l.rSum+r.lSum)
public static MaxSubArray pushUp(MaxSubArray l, MaxSubArray r) {
int iSum = l.iSum + r.iSum;
int lSum = Math.max(l.lSum, l.iSum + r.lSum);
int rSum = Math.max(r.rSum, r.iSum + l.rSum);
int mSum = Math.max(Math.max(l.mSum, r.mSum), l.rSum + r.lSum);// 三者取最大
// 返回结果
return new MaxSubArray(lSum, rSum, mSum, iSum);
}
这个分治方法类似于「线段树求解最长公共上升子序列问题」的 pushUp 操作。
可以借助着方法进行理解记忆
代码及运行结果:
package day1;
import java.util.Scanner;
//最大子数组和
public class MaxSubArray {
// lSum 表示 [l,r]内以 l 为左端点的最大子段和
// rSum 表示 [l,r]内以 r 为右端点的最大子段和
// mSum 表示 [l,r]内的最大子段和,最终解
// iSum 表示 [l,r]的区间和
public int lSum, rSum, mSum, iSum;
// 有参构造器,更新值
public MaxSubArray(int lSum, int rSum, int mSum, int iSum) {
this.lSum = lSum;
this.rSum = rSum;
this.mSum = mSum;
this.iSum = iSum;
}
// 定义入口,调用getInfo()
public static int maxSubArray(int[] nums) {
return getInfo(nums, 0, nums.length - 1).mSum;
}
public static MaxSubArray getInfo(int[] a, int l, int r) {
if (l == r) {// 递归出口
return new MaxSubArray(a[l], a[l], a[l], a[l]);
}
// 递归函数
int m = (l + r) / 2;
MaxSubArray lSub = getInfo(a, l, m);// 左子区间
MaxSubArray rSub = getInfo(a, m + 1, r);// 右子区间
return pushUp(lSub, rSub);
}
public static MaxSubArray pushUp(MaxSubArray l, MaxSubArray r) {
int iSum = l.iSum + r.iSum;
int lSum = Math.max(l.lSum, l.iSum + r.lSum);
int rSum = Math.max(r.rSum, r.iSum + l.rSum);
int mSum = Math.max(Math.max(l.mSum, r.mSum), l.rSum + r.lSum);// 三者取最大
// 返回结果
return new MaxSubArray(lSum, rSum, mSum, iSum);
}
public static void main(String[] args) {
// 输入一个数组
System.out.println("输入一个数组");
Scanner scanner = new Scanner(System.in);
String str = scanner.next().toString();
String[] s = str.split(",");
int[] a = new int[s.length];
for (int i = 0; i < a.length; i++) {
a[i] = Integer.parseInt(s[i]);
}
// 输出结果
int r = maxSubArray(a);
System.out.println(r);
scanner.close();
}
}