分治算法总结
分治法由两部分组成:
- 分:递归解决较小的问题
- 治:然后从子问题的解构建原问题的解
传统上,至少包含两个递归调用的程序才叫做分治算法。一般分出来的子问题不相交。
例题一:Leetcode 53. 最大子序和
算法分析:
-
将区间等分。具有最大和的连续子数组要么在左半区间,要么在右半区间,要么横跨两端(从左区间的某个数到右区间的某个数)
-
当最大子数组有 n 个数字时:
- 若 n==1,返回此元素
- left_sum 为最大子数组前 n/2 个元素,在索引为 (left + right) / 2 的元素属于左子数组
- right_sum 为最大子数组的右子数组,为最后 n/2 的元素
- cross_sum 是包含左右子数组且含索引 (left + right) / 2 的最大值
-
参考链接
- 官方题解(Leetcode官方题解)
- ALEIx题解(ALEIx题解)
- liweiwei1419题解(liweiwei1419题解)
public class Solution {
public int maxSubArray(int[] nums) {
int len = nums.length;
if (len == 0) {
return 0;
}
return maxSubArraySum(nums, 0, len - 1);
}
//找寻区间内最大子数组和
//最大子数组要么在左区间,要么横跨整个区间,要么在右区间
public int maxSubArraySum(int[] nums, int left, int right) {
//区间只有1个数
if (left == right) {
return nums[left];
}
//int mid = (left + right)/2;
int mid = (left + right) >>> 1;
//比较左区间、整个区间、右区间
return findMaxMethod( maxSubArraySum(nums, left, mid),maxCrossingSum(nums, left, mid, right),maxSubArraySum(nums, mid+1, right) );
}
//找到3区间的最大和
public int findMaxMethod(int num1, int num2, int num3) {
return Math.max(num1, Math.max(num2, num3));
}
//横跨整个区间
//一定会包含 nums[mid] 这个元素
//示意图:*表示构成连续子数组的元素[left,x,x,x,*,mid,*,*,*,x,right]
//横跨整个区间的和 = 从mid往左的部分 + 从mid+1往右边的部分
//maxCrossingSum = leftSum + rightSum
public int maxCrossingSum(int[] nums, int left, int mid, int right) {
int sum = 0;
int leftSum = Integer.MIN_VALUE;
// 左半边包含 nums[mid] 元素,最多可以到什么地方
// 走到最边界,看看最值是什么
// 计算以 mid 结尾的最大的子数组的和
for (int i = mid; i >= left; i--) {
sum += nums[i];
if (sum > leftSum) {
leftSum = sum;
}
}
sum = 0;
int rightSum = Integer.MIN_VALUE;
// 右半边不包含 nums[mid] 元素,最多可以到什么地方
// 计算以 mid+1 开始的最大的子数组的和
for (int i = mid + 1; i <= right; i++) {
sum += nums[i];
if (sum > rightSum) {
rightSum = sum;
}
}
return leftSum + rightSum;
}
}
执行结果:
执行用时 :3 ms, 在所有 java 提交中击败了13.06%的用户
内存消耗 :38.7 MB, 在所有 java 提交中击败了82.04%的用户
例题二:Leetcode 4. 寻找两个有序数组的中位数
算法分析:
- 在统计中,中位数被用来
将一个集合划分为两个长度相等的子集,其中一个子集中的元素总是大于另一个子集中的元素
- 如果观察值有奇数个,通常取正中间的一个作为中位数
- 如果观察值有偶数个,通常取最中间的两个数值的平均数作为中位数
- 对于有序数组A(偶数个元素),在位置 i 将 A 划分成两个部分,A[i]放右边
left_A | right_A A[0], A[1], ..., A[i-1] | A[i], A[i+1], ..., A[m-1]
- len(left_A)=len(right_A)=i
- max(left_A)≤min(right_A)
- 我们已经将 A中的所有元素划分为相同长度的两个部分,且其中一部分中的元素总是大于另一部分中的元素
- 对于有序数组B(偶数个元素),在位置 j 将 B 划分成两个部分,B[j]放右边
left_B | right_B B[0], B[1], ..., B[j-1] | B[j], B[j+1], ..., B[n-1]
- len(left_B)=len(right_B)=j
- max(left_B)≤min(right_B)
- 我们已经将 B中的所有元素划分为相同长度的两个部分,且其中一部分中的元素总是大于另一部分中的元素
- 对于有序数组A和有序数组B,将 left_A 和 left_B 放入一个集合,并将 right_A 和 right_B 放入另一个集合。 再把这两个新的集合分别命名为 left_part 和 right_part
left_part | right_part A[0], A[1], ..., A[i-1], | A[i], A[i+1], ..., A[m-1], B[0], B[1], ..., B[j-1] | B[j], B[j+1], ..., B[n-1]
如果能够保证:
- len(left_part)=len(right_part)
- max(left_part)≤min(right_part)
- 那么,我们已经将 {A,B} 中的所有元素划分为相同长度的两个部分,且其中一部分中的元素总是大于另一部分中的元素。
此时需要满足的条件:
- i + j =(m - i) + (n - j) ——> left_part和right_part长度相等
- B[ j - 1] <= A[ i ] 并且A[ i - 1] <= B[ j ]——>left_part中最大值小于等于right_part最小值
- 由已知条件,B[ j - 1] <= B[ j ] 并且A[ i - 1] <= A[ i ]
上述分析的前提都是数组元素个数为偶数,如果为奇数,则第一个条件改为
- i + j =(m - i) + (n - j) +1 ——> left_part 比 right_part 长度大1,中间元素放在左面
如何不论数组元素个数的奇偶性呢?
- 为简化分析,要求n>=m
- 如果n >=m ,只要 i=0 ~ m ,则j=(m+ n+1)/2 -i
public double findMedianSortedArrays(int[] A, int[] B) {
int m = A.length;
int n = B.length;
if (m > n) { // to ensure m<=n
int[] temp = A; A = B; B = temp;
int tmp = m; m = n; n = tmp;
}
int iMin = 0, iMax = m, halfLen = (m + n + 1) / 2;
while (iMin <= iMax) {
int i = (iMin + iMax) / 2;
int j = halfLen - i;
if (i < iMax && B[j - 1] > A[i]) {
iMin = i + 1; // i is too small
} else if (i > iMin && A[i - 1] > B[j]) {
iMax = i - 1; // i is too big
} else { // i is perfect
int maxLeft;
if (i == 0) {//A分成的leftA(空集) 和 rightA(A的全部) 所以leftPart = leftA(空集) + leftB,故maxLeft = B[j-1]。
maxLeft = B[j - 1];
} else if (j == 0) { //B分成的leftB(空集) 和 rightB(B的全部) 所以leftPart = leftA + leftB(空集),故maxLeft = A[i-1]。
maxLeft = A[i - 1];
} else { //排除上述两种特殊情况,正常比较
maxLeft = Math.max(A[i - 1], B[j - 1]);
}
if ((m + n) % 2 == 1) { //奇数,中位数正好是maxLeft
return maxLeft;
}
//偶数
int minRight;
if (i == m) {//A分成的leftA(A的全部) 和 rightA(空集) 所以rightPart = rightA(空集) + rightB,故minRight = B[j]。
minRight = B[j];
} else if (j == n) {//B分成的leftB(B的全部) 和 rightB(空集) 所以rightPart = rightA + rightB(空集),故minRight = A[i]。
minRight = A[i];
} else {//排除上述两种特殊情况,正常比较
minRight = Math.min(B[j], A[i]);
}
return (maxLeft + minRight) / 2.0;
}
}
return 0.0;
}
- 参考链接 —— 官方题解(Leetcode官方题解)