53. 最大子数组和(剑指 Offer 42. 连续子数组的最大和)
题目:
思路:
【1】贪心的思路
【2】进阶的分治法
它不仅可以解决区间 [0,n−1],还可以用于解决任意的子区间 [l,r] 的问题。
如果我们把 [0,n−1] 分治下去出现的所有子区间的信息都用堆式存储的方式记忆化下来,即建成一棵真正的树之后,
我们就可以在 O(logn)的时间内求到任意区间内的答案,
我们甚至可以修改序列中的值,做一些简单的维护,之后仍然可以在 O(logn) 的时间内求到任意区间内的答案,
对于大规模查询的情况下,这种方法的优势便体现了出来
代码展示:
分治法的代码实现:
//时间7 ms击败5.5% //内存50.8 MB击败48.95% //时间复杂度:假设我们把递归的过程看作是一颗二叉树的先序遍历, //那么这颗二叉树的深度的渐进上界为 O(logn),这里的总时间相当于遍历这颗二叉树的所有节点, //故总时间的渐进上界是 O(n),故渐进时间复杂度为 O(n)。 //空间复杂度:递归会使用 O(logn)的栈空间,故渐进空间复杂度为 O(logn)。 class Solution { public class Status { public int lSum, rSum, mSum, iSum; public Status(int lSum, int rSum, int mSum, int iSum) { // lSum 表示 [l,r] 内以 l 为左端点的最大子段和 this.lSum = lSum; // rSum 表示 [l,r] 内以 rrr 为右端点的最大子段和 this.rSum = rSum; // mSum 表示 [l,r] 内的最大子段和 this.mSum = mSum; // iSum 表示 [l,r] 的区间和 this.iSum = iSum; } } public int maxSubArray(int[] nums) { return getInfo(nums, 0, nums.length - 1).mSum; } public Status getInfo(int[] a, int l, int r) { if (l == r) { return new Status(a[l], a[l], a[l], a[l]); } // 进行递归二分 int m = (l + r) >> 1; Status lSub = getInfo(a, l, m); Status rSub = getInfo(a, m + 1, r); // 将二分进行合并 return pushUp(lSub, rSub); } // 数据示例【-2,1,-3,4,-1,2,1,-5,4】 // 过程合并 //合并前:Status{lSum=-2, rSum=-2, mSum=-2, iSum=-2} Status{lSum=1, rSum=1, mSum=1, iSum=1} //合并后:Status{lSum=-1, rSum=1, mSum=1, iSum=-1} //合并前:Status{lSum=-1, rSum=1, mSum=1, iSum=-1} Status{lSum=-3, rSum=-3, mSum=-3, iSum=-3} //合并后:Status{lSum=-1, rSum=-2, mSum=1, iSum=-4} //合并前:Status{lSum=4, rSum=4, mSum=4, iSum=4} Status{lSum=-1, rSum=-1, mSum=-1, iSum=-1} //合并后:Status{lSum=4, rSum=3, mSum=4, iSum=3} //合并前:Status{lSum=-1, rSum=-2, mSum=1, iSum=-4} Status{lSum=4, rSum=3, mSum=4, iSum=3} //合并后:Status{lSum=0, rSum=3, mSum=4, iSum=-1} //合并前:Status{lSum=2, rSum=2, mSum=2, iSum=2} Status{lSum=1, rSum=1, mSum=1, iSum=1} //合并后:Status{lSum=3, rSum=3, mSum=3, iSum=3} //合并前:Status{lSum=-5, rSum=-5, mSum=-5, iSum=-5} Status{lSum=4, rSum=4, mSum=4, iSum=4} //合并后:Status{lSum=-1, rSum=4, mSum=4, iSum=-1} //合并前:Status{lSum=3, rSum=3, mSum=3, iSum=3} Status{lSum=-1, rSum=4, mSum=4, iSum=-1} //合并后:Status{lSum=3, rSum=4, mSum=4, iSum=2} //合并前:Status{lSum=0, rSum=3, mSum=4, iSum=-1} Status{lSum=3, rSum=4, mSum=4, iSum=2} //合并后:Status{lSum=2, rSum=5, mSum=6, iSum=1} public Status pushUp(Status l, Status 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 Status(lSum, rSum, mSum, iSum); } }
贪心的思路代码实现:
//时间1 ms击败100% //内存50.2 MB击败94.22% //时间复杂度:O(n),其中 n 为 nums 数组的长度。我们只需要遍历一遍数组即可求得答案。 //空间复杂度:O(1)。我们只需要常数空间存放若干变量。 class Solution { public int maxSubArray(int[] nums) { if (nums == null || nums.length == 0) { return 0; } int length = nums.length; int cur = nums[0]; int max = nums[0]; for (int i = 1; i < length; i++) { //这里如果之前的和为负数,那么则会被抛弃,改为取0,并加上下一个数而得到当前的和 cur = Math.max(cur, 0) + nums[i]; //记录最大值,判断当前的和与之前的最大数是否要进行替换 max = Math.max(max, cur); } return max; } }
如果需要返回子序列数组的话:
public static int maxSubArray(int[] nums) { if (nums == null || nums.length == 0) { return 0; } int length = nums.length; int tab = 0; int cur = nums[0]; int max = nums[0]; for (int i = 1; i < length; i++) { cur = Math.max(cur, 0) + nums[i]; //记录最大值 max = Math.max(max, cur); if (nums[i] == cur && max == nums[i]){ tab = i; } } //若是还需要返回对应的子序列数组的话,还可以添加以下代码 ArrayList<Integer> arr = new ArrayList<>(); int ma = max; for (int i = tab; i < length; i++) { arr.add(nums[i]); ma-=nums[i]; if (ma == 0){ System.out.println(); break; } } return max; }