归并排序面试题第二篇

136 阅读2分钟

「这是我参与2022首次更文挑战的第30天,活动详情查看:2022首次更文挑战」。

一、题目一

在一个数组中, 对于每个数num,求有多少个后面的数 * 2 依然<num,求总个数 比如:[3,1,7,0,2] 3的后面有:1,0 1的后面有:0 7的后面有:0,2 0的后面没有 2的后面没有 所以总共有5个

LeetCode

1、分析

右侧数乘2比左边数小,有多少个数达标(num的右边有多少个数*2后依然小于num)

依然是merge的过程,

image.png

不回退的技巧,因为左边有序,右边有序,必然不回退

image.png

2、实现

public static int reversePairs(int[] arr) {
    if (arr == null || arr.length < 2) {
        return 0;
    }
    return process(arr, 0, arr.length - 1);
}

public static int process(int[] arr, int L, int R) {
    if (L == R) {
        return 0;
    }
    int M = L + ((R - L) >> 1);
    return process(arr, L, M) + process(arr, M + 1, R) + merge(arr, L, M, R);
}

public static int merge(int[] arr, int L, int M, int R) {
    // [L....M] [M+1....R]
    int ans = 0;
    // 目前囊括进来的数,是从[M+1, windowR)
    int windowR = M + 1;
    for (int i = L; i <= M; i++) { // 左边部分
        while (windowR <= R && (long) arr[i] > ((long) arr[windowR] * 2)) {
            windowR++;
        }
        ans += windowR - M - 1;
    }
    int[] help = new int[R - L + 1];
    int p1 = L;
    int p2 = M + 1;
    int index = 0;
    while (p1 <= M && p2 <= R) {
        help[index++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
    }
    while (p1 <= M) {
        help[index++] = arr[p1++];
    }
    while (p2 <= R) {
        help[index++] = arr[p2++];
    }
    for (int i = 0; i < help.length; i++) {
        arr[L + i] = help[i];
    }
    return ans;
}

二、题目二

给定一个数组arr,两个整数lower和upper,返回arr中有多少个子数组的累加和在[lower,upper]范围上

LeetCode

1、分析

前置知识:数组前缀和的应用,对外提供一种操作(getSum(arr, i, j) ),这种操作特别频繁,需要时间复杂度O(1),任何i到j位置的累加和是多少

sum[i...j] = sum[0...j] - sum[0...i-1]

子数组必须是连续的

暴力解分析:时间复杂度O(N³)

暴力解优化,利用提前生成好的数组前缀和,替代每次遍历验证,可以优化为O(N²)

不够,还有更优化的解法!

换个思路,任何子数组都有结尾树枝,所以任何以i为结尾的子数组,有几个达标(在[lower,up]范围上),最后的结果 = 所有达标的个数累加起来

arr[i...j]在[lower,up]范围上是达标的,等价于 arr[0...j] - arr[0...i-1]在[lower,up]范围上也是达标的

假设0~i整体累加和是x,题目[lower,up],求必须以i位置结尾的子数组,有多少个在[lower,up]范围上,等同于,去求i之前的所有前缀和中有多少个前缀和在[x-up,x-lower]上

2、实现

public static int countRangeSum(int[] nums, int lower, int upper) {
    if (nums == null || nums.length == 0) {
        return 0;
    }
    long[] sum = new long[nums.length];
    sum[0] = nums[0];
    for (int i = 1; i < nums.length; i++) {
        sum[i] = sum[i - 1] + nums[i];
    }
    return process(sum, 0, sum.length - 1, lower, upper);
}

public static int process(long[] sum, int L, int R, int lower, int upper) {
    if (L == R) {
        return sum[L] >= lower && sum[L] <= upper ? 1 : 0;
    }
    int M = L + ((R - L) >> 1);
    return process(sum, L, M, lower, upper) + process(sum, M + 1, R, lower, upper)
            + merge(sum, L, M, R, lower, upper);
}

三、总结

题目一:

  • 不回退技巧
  • 数组中只要涉及到左右大小问题,可以利用归并排序merge过程,进一步解决

题目二:

mergeSort改下,有点难!!!

  • 前缀和搞事情
  • 原[lower,upper] 转化为 前缀和数组x,求[x-upper,x-lower]
  • sum[]上,x之前有多少个在[x-upper,x-lower]范围上
  • 归并排序,mergeSort上改

有序表解的话,好理解!!!