使用分治法需满足的条件
- 原问题可以分解为若干个规模较小的子问题
- 子问题互相独立
- 子问题的解合并处理后可得到原问题的解
LeetCode-53:Maximum subarray
给定一个整数数组 nums,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例:
输入: [-2,1,-3,4,-1,2,1,-5,4],
输出: 6
解释:连续子数组[4,-1,2,1] 的和最大,为6
普通O(n)
暴力求解的话,第一层是n个元素的循环,第二层是每个元素与其后面的所有元素之和,记录最大值。共计算 n n * 次,时间复杂度O(n 2 2 )
思路
借鉴自discuss
观察可发现,当第
Xi
X
i
数的值大于
X0
X
0
~
Xi−1
X
i
−
1
之和时,就应该抛弃
X0
X
0
~
Xi−1
X
i
−
1
d的和(但是不代表该sum值不是最大值,只是开始新的计算);反之,
Xi
X
i
的值由
X0
X
0
~
X−1
X
−
1
之和代替
比如说:[-2,1,-3,4,-1,2,1,-5,4]
- -2+1=-1<1,所以没必要参与接下来的计算。 X1 X 1 的值还是1;
- 1+-3=-2>-3。替换 X2 X 2 的值为-2;
- -2+4=2<4,故 X3 X 3 的值还是4;
- 。。。。。。计算出最大值为6;
- 由于不需要求出子序列是什么,只要求结果,故可使用该时间复杂度为O(n)的算法。
白纸代码
Ac代码
【暂未使用Junit写单元测试】
public class MaxSumSubArray {
public static int maxSumValueSubArray(int[] nums) {
int max = nums[0];
if (nums.length == 1) {
return max;
}
for (int i = 0; i < nums.length - 1; i++) {
nums[i + 1] = Math.max(nums[i + 1], nums[i] + nums[i + 1]);
max = Math.max(max, nums[i + 1]);
}
return max;
}
public static void main(String[] args) {
int[] nums1 = {-2, 1, -3, 4, -1, 2, 1, -5, 4};
int[] nums2 = {-5, 4};
int[] nums3 = {6, -2, 0};
System.out.println(MaxSumSubArray.maxSumValueSubArray(nums3));
}
}
优于82.72%的提交结果
分治法求解
思路
- 将原问题分解为众多子问题,子问题求的目标与总问题相同,那么最终的解一定可以由这些子问题中解推演得到
- 针对此题,当子问题划分到只有两个数字,比如6, -3。那么,答案是6(左半部分值)、-3(右半部分值)和6+(-3)=3(两部分之和)这三种值中的最大值
- 根据2,可递推得:最大值是左半部分最大值、右半部分最大值、左半部分的后缀最大值与右半部分的前缀最大值之和这三个值中的最大值
- 因为每次问题变为上一次的一半,所以总共分解 ⌈ ⌈ log 2 2 n ⌉ ⌉ 次。
稿纸示意图
白纸测试代码
注:@test改为@Test
public class MaxSumSubArrayDivideTest {
@Test
public void testMaxSubArrayDivide_1() throws Exception {
MaxSumSubArrayDivide maxSumSubArrayDivide = new MaxSumSubArrayDivide();
int[] nums = {-3};
int expected = nums[0];
assertEquals("Wrong!Missmatch!", expected, maxSumSubArrayDivide.maxSubArray(nums));
}
@Test
public void testMaxSubArrayDivide_2() throws Exception {
MaxSumSubArrayDivide maxSumSubArrayDivide = new MaxSumSubArrayDivide();
int[] nums = {-2,1,-3,4,-1,2,1,-5,4};
int expected = 6;
assertEquals("Wrong!Missmatch!", expected, maxSumSubArrayDivide.maxSubArray(nums));
}
}
白纸代码
Ac代码
public class MaxSumSubArrayDivide {
public int maxSubArray(int[] nums) {
if (nums.length == 1) {
return nums[0];
} else return maxSubArrayDivide(nums, 0, nums.length - 1);
}
private int maxSubArrayDivide(int[] nums, int from, int to) {
// 递归出口
if (from == to) {
return nums[from];
}
int middle = (from + to) / 2;
// left recursive call
int left = maxSubArrayDivide(nums, from, middle);
// right recursive call
int right = maxSubArrayDivide(nums, middle + 1, to);
// 开始O(n)时间复杂度的计算,"叶子"返回后,需要计算第三种可能:左半部分的后缀+右半部分的前缀之和为最大值
int leftEnd = nums[middle], leftSuffix = leftEnd;
int rightBegin = nums[middle + 1], rightPrefix = rightBegin, i = 1;
// 计算左半部分的最大后缀
while (middle - i >= from) {
leftEnd += nums[middle - i++];
leftSuffix = Math.max(leftSuffix, leftEnd);
}
// 计算右半部分的最大前缀
i = 2;
while (middle + i <= to) {
rightBegin += nums[middle + i++];
rightPrefix = Math.max(rightPrefix, rightBegin);
}
int join = leftSuffix + rightPrefix;
return Math.max(left, Math.max(right, join));
}
}
分治算法复杂度求解
所以该实现比之前的O(n)要差一些。
“击败“13%的AC提交。。。