左神算法笔记-归并分治

70 阅读1分钟

力扣链接:leetcode.cn/problems/re…

原理

  • 思考一个问题在大范围上的答案是否等于:左部分答案 + 右部分答案 + 跨左右产生的答案
  • 计算 “跨左右产生的答案” 时,若加上左、右各自有序这个设定,会不会获得计算的便利性
  • 以上两点都成立,则大概率可以使用归并分治解决

代码

class Solution {
    /**
     * 数组最大长度,取自题目限定值
     */
    public static int MAXN = 50001;
    /**
     * 辅助数组,归并排序用
     */
    public static int[] help = new int[MAXN];

    public int reversePairs(int[] nums) {
        return counts(nums, 0, nums.length - 1);
    }

    /**
     * 统计l...r范围上,翻转对的数量,同时l...r范围统计完后变有序
     *
     * @param nums 数组
     * @param l    左下标
     * @param r    右下标
     * @return int
     */
    public int counts(int[] nums, int l, int r) {
        if (l == r) {
            return 0;
        }
        int m = (l + r) / 2;
        // 左部分答案 + 右部分答案 + 跨左右产生的答案,可以用分治思想
        return counts(nums, l, m) + counts(nums, m + 1, r) + merge(nums, l, m, r);
    }

    public int merge(int[] nums, int l, int m, int r) {
        // 统计跨左右的数量
        int ans = 0;
        for (int i = l, j = m + 1; i <= m; i++) {
            // l .... m m+1 .... r
            // i         j
            while (j <= r && (long) nums[i] > (long) 2 * nums[j]) {
                j++;
            }
            ans += j - (m + 1);
        }

        // 左右两边统计完就归并排序
        int i = l;
        int a = l;
        int b = m + 1;
        while (a <= m && b <= r) {
            help[i++] = nums[a] <= nums[b] ? nums[a++] : nums[b++];
        }
        // 左侧指针、右侧指针,必有一个越界、另一个不越界
        while (a <= m) {
            help[i++] = nums[a++];
        }
        while (b <= r) {
            help[i++] = nums[b++];
        }
        // 复制到原数组
        for (i = l; i <= r; i++) {
            nums[i] = help[i];
        }
        return ans;
    }
}