分治(Divide And Conquer)
分治,也就是分而治之。它的一般步骤是:
① 将原问题分解成若干个规模较小的子问题(子问题和原问题的结构一样,只是规模不一样)
② 子问题又不断分解成规模更小的子问题,直到不能再分解(直到可以轻易计算出子问题的解)
③ 利用子问题的解推导出原问题的解
因此,分治策略非常适合用递归。
需要注意的是:子问题之间是相互独立的
分治的应用:
- 快速排序
- 归并排序
- Karatsuba 算法(大数乘法)
主定理(Master Theorem)
对n个数据排序:O(n2)
那么对n/2个数据排序:O(n2/4)
分治: O(n2/4) * 2 + merge
只需要保证merge< O(n2/2) 即可
问题1:最大连续子序列和
题目:leetcode_53_最大子序和
给定一个长度为 n 的整数序列,求它的最大连续子序列和
比如 –2、1、–3、4、–1、2、1、–5、4 的最大连续子序列和是 4 + (–1) + 2 + 1 = 6
这道题也属于最大切片问题(最大区段,Greatest Slice)
概念区分
- 连续子序列、子串、子数组、子区间:必须是连续的
- 子序列:可以不连续
解法1 – 暴力出奇迹
穷举出所有可能的连续子序列,分别计算出它们的和,最后取它们中的最大值。
package 分治算法;
public class Main {
public static void main(String[] args) {
int[] nums = {-2,1,-3,4,-1,2,1,-5,4};
System.out.println(maxSubArray(nums));
}
static int maxSubArray(int [] nums) {
if (nums == null || nums.length == 0) return 0;
// 这里注意, 容易写成 int max = 0, 可能会出错, max 默认值必须是最小的值
int max = Integer.MIN_VALUE;
// 穷举, 列出所有可能的连续子序列, 分别计算它们的和, 最后取出最大值
for (int begin = 0; begin < nums.length; begin++) {
for (int end = begin; end < nums.length; end++) {
int sum = 0;
for (int i = begin; i <= end ; i++) {
sum+= nums[i];
}
if(sum > max) max = sum;
}
}
return max;
}
}
优化1:减少重复计算
算法缺陷:重复计算太多!
[begin,end] 和 [begin,end+1]重复计算了[begin,end],现在考虑优化,让[begin,end]的值被记住
//todo 优化1 减去重复计算 [begin,end] 的值作为[begin,end+1]
static int maxSubArray2(int [] nums) {
if (nums == null || nums.length == 0) return 0;
// 这里注意, 容易写成 int max = 0, 可能会出错, max 默认值必须是最小的值
int max = Integer.MIN_VALUE;
// 穷举, 列出所有可能的连续子序列, 分别计算它们的和, 最后取出最大值
for (int begin = 0; begin < nums.length; begin++) {
int sum = 0;
for (int end = begin; end < nums.length; end++) {
sum += nums[end];
}
if(sum > max) max = sum;
}
return max;
}
解法2:分治
package 分治算法;
public class Main {
public static void main(String[] args) {
int[] nums = {-2,1,-3,4,-1,2,1,-5,4};
System.out.println(maxSubArray(nums));
}
static int maxSubArray(int [] nums) {
if (nums == null || nums.length == 0) return 0;
// 这里注意, 容易写成 int max = 0, 可能会出错, max 默认值必须是最小的值
int max = Integer.MIN_VALUE;
// 穷举, 列出所有可能的连续子序列, 分别计算它们的和, 最后取出最大值
for (int begin = 0; begin < nums.length; begin++) {
for (int end = begin; end < nums.length; end++) {
int sum = 0;
for (int i = begin; i <= end ; i++) {
sum+= nums[i];
}
if(sum > max) max = sum;
}
}
return max;
}
//todo 优化1 减去重复计算 [begin,end] 的值作为[begin,end+1]
static int maxSubArray2(int [] nums) {
if (nums == null || nums.length == 0) return 0;
// 这里注意, 容易写成 int max = 0, 可能会出错, max 默认值必须是最小的值
int max = Integer.MIN_VALUE;
// 穷举, 列出所有可能的连续子序列, 分别计算它们的和, 最后取出最大值
for (int begin = 0; begin < nums.length; begin++) {
int sum = 0;
for (int end = begin; end < nums.length; end++) {
sum += nums[end];
}
if(sum > max) max = sum;
}
return max;
}
//todo 3.分治解法
//函数功能:求出nums中begin到end之间的最大连续子序列的和
static int maxSubArray3(int [] nums,int begin,int end) {
if (nums == null || nums.length == 0) return 0;
//begin到end只有一个元素的时候
if(end - begin < 2) return nums[begin];
int mid = (begin + end) >> 1;
int leftMax = Integer.MIN_VALUE;
int leftSum = Integer.MIN_VALUE;
for(int i = mid-1;i>= begin;i--){
leftSum += nums[i];
leftMax = Math.max(leftMax,leftSum);
}
int rightMax = 0;
int rightSum = 0;
for(int i = mid;i < end;i++){
rightSum += nums[i];
rightMax = Math.max(rightMax,rightSum);
}
//1.左边的最大
//2.右边的
//3.两边在一起的
return Math.max(leftMax + rightMax,
Math.max(maxSubArray3(nums,begin,mid),maxSubArray3(nums,mid,end)));
}
}
大数乘法
136
* 254
----------
24
300
1200
120
1500
6000
400
5000
20000
两个n规模的数字相乘 =》 T(n)
分治:将n规模的数据相乘分解成了3个(n/2)规模的数据相乘