分治算法总结(更新中)

分治算法总结


分治法由两部分组成:

  • 分:递归解决较小的问题
  • 治:然后从子问题的解构建原问题的解

传统上,至少包含两个递归调用的程序才叫做分治算法。一般分出来的子问题不相交。

例题一:Leetcode 53. 最大子序和

例题1

算法分析:

  • 将区间等分。具有最大和的连续子数组要么在左半区间,要么在右半区间,要么横跨两端(从左区间的某个数到右区间的某个数)

  • 当最大子数组有 n 个数字时:

    • 若 n==1,返回此元素
    • left_sum 为最大子数组前 n/2 个元素,在索引为 (left + right) / 2 的元素属于左子数组
    • right_sum 为最大子数组的右子数组,为最后 n/2 的元素
    • cross_sum 是包含左右子数组且含索引 (left + right) / 2 的最大值
  • 参考链接

在这里插入图片描述

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. 寻找两个有序数组的中位数

在这里插入图片描述

算法分析:

  • 在统计中,中位数被用来

将一个集合划分为两个长度相等的子集,其中一个子集中的元素总是大于另一个子集中的元素

  • 如果观察值有奇数个,通常取正中间的一个作为中位数
  • 如果观察值有偶数个,通常取最中间的两个数值的平均数作为中位数
  1. 对于有序数组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中的所有元素划分为相同长度的两个部分,且其中一部分中的元素总是大于另一部分中的元素
  1. 对于有序数组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中的所有元素划分为相同长度的两个部分,且其中一部分中的元素总是大于另一部分中的元素
  1. 对于有序数组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;
    }
  1. 参考链接 —— 官方题解(Leetcode官方题解)
发布了15 篇原创文章 · 获赞 4 · 访问量 735

猜你喜欢

转载自blog.csdn.net/weixin_38938338/article/details/103677479