归并排序+面试题

归并排序

1)整体是递归,左边排好序+右边排好序+merge让整体有序

2)让其整体有序的过程里用了排外序方法

3)利用master公式来求解时间复杂度

4)当然可以用非递归实现

归并排序复杂度

T(N) = 2*T(N/2) + O(N^1)

根据master可知时间复杂度为O(N*logN)

merge过程需要辅助数组,所以额外空间复杂度为O(N)

归并排序的实质是把比较行为变成了有序信息并传递,比O(N^2)的排序快

代码实现

package com.lzf2.class03;

/**
 * 归并排序
 */
public class MergeSort {
    //1.递归版本
    public static void sort(int[] arr){
        if(arr == null || arr.length ==0){
            return;
        }
        process(arr,0,arr.length-1);
    }
    //请把 L -- R位置排序
    private static void process(int[] arr,int L,int R){
        //base case
        if(L == R){
            return;
        }
        int mid = L +((R - L) >> 1);
        //左边排序
        process(arr,L,mid);
        //右边排序
        process(arr,mid + 1,R);
        //合并
        merge(arr,L,mid,R);
    }
    private static void merge(int[] arr,int L,int mid,int R){
        int[] help = new int[R - L + 1];
        int i = 0;
        int left = L;
        int right = mid + 1;
        while (left <= mid && right<= R){
            help[i++] = arr[left] < arr[right]?arr[left++]:arr[right++];
        }
        while (left <= mid){
            help[i++] = arr[left++];
        }
        while (right <= R){
            help[i++] = arr[right++];
        }
        //复制到原数组
        for (int j = 0; j < help.length; j++) {
            arr[L + j] = help[j];
        }
    }



    //2.非递归版本
    public static void sort2(int[] arr) {
        if (arr == null || arr.length < 2) {
            return;
        }
        int N = arr.length;
        //步长
        int mergeSize = 1;
        while (mergeSize < N) {

            //当前左组的,第一个位置
            int L = 0;
            while (L < N) {//每轮步长要做的事
                int M = L + mergeSize - 1;//左组最后一个数
                if (M >= N) {//只有左组数,没有右组数,不用继续做了
                    break;
                }
                int R = Math.min(M + mergeSize, N - 1);//右组的最后一个数
                merge(arr, L, M, R);
                L = R + 1;//去搞下一个左组
            }
            //防止溢出
            if (mergeSize > N / 2) {
                break;
            }

            //步长乘2
            mergeSize <<= 1;
        }
    }
    // for test
    public static int[] generateRandomArray(int maxSize, int maxValue) {
        int[] arr = new int[(int) ((maxSize + 1) * Math.random())];
        for (int i = 0; i < arr.length; i++) {
            arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());
        }
        return arr;
    }

    // for test
    public static int[] copyArray(int[] arr) {
        if (arr == null) {
            return null;
        }
        int[] res = new int[arr.length];
        for (int i = 0; i < arr.length; i++) {
            res[i] = arr[i];
        }
        return res;
    }

    // for test
    public static boolean isEqual(int[] arr1, int[] arr2) {
        if ((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null)) {
            return false;
        }
        if (arr1 == null && arr2 == null) {
            return true;
        }
        if (arr1.length != arr2.length) {
            return false;
        }
        for (int i = 0; i < arr1.length; i++) {
            if (arr1[i] != arr2[i]) {
                return false;
            }
        }
        return true;
    }

    // for test
    public static void printArray(int[] arr) {
        if (arr == null) {
            return;
        }
        for (int i = 0; i < arr.length; i++) {
            System.out.print(arr[i] + " ");
        }
        System.out.println();
    }

    // for test
    public static void main(String[] args) {
        int testTime = 100000;
        int maxSize = 100;
        int maxValue = 100;
        System.out.println("测试开始");
        for (int i = 0; i < testTime; i++) {
            int[] arr1 = generateRandomArray(maxSize, maxValue);
            int[] arr2 = copyArray(arr1);
            sort(arr1);
            sort2(arr2);
            if (!isEqual(arr1, arr2)) {
                System.out.println("出错了!");
                printArray(arr1);
                printArray(arr2);
                break;
            }
        }
        System.out.println("测试结束");
    }
}

第一题

在一个数组中,一个数左边比它小的数的总和,叫数的小和,所有数的小和累加起来,叫数组小和。求数组小和。

例子: [1,3,4,2,5]

1左边比1小的数:没有

3左边比3小的数:1

4左边比4小的数:1、3

2左边比2小的数:1

5左边比5小的数:1、3、4、 2

所以数组的小和为1+1+3+1+1+3+4+2=16

(即:求每个数右边右多少个数比它大)

代码实现

package com.lzf2.class03;

/**
 * 小和问题  (即:求每个数右边右多少个数比它大)
 */
public class SmallSum {

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

    //递归过程
    private static int process(int[] arr,int L,int R){
        //base case
        if(L == R){
            return 0;
        }
        int mid = L + ((R - L) >> 1);
        return process(arr,L,mid) + process(arr,mid + 1,R) +merge(arr,L,mid,R);
    }

    private static int merge(int[] arr,int L ,int M , int R){
        int[] help = new int[R - L + 1];
        int i =0;
        int res = 0;
        //拷贝右边不产生小和,相等时拷贝右边。
        int p1 = L;
        int p2 = M + 1;
        //两个都不越界时
        while (p1 <= M && p2<= R){
            //是否产生小和
            res += arr[p1] < arr[p2] ? (R - p2 + 1) * arr[p1]:0;
            //拷贝过程,相等考右边
            help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
        }
        while (p1 <= M){
            help[i++] = arr[p1++];
        }
        while (p2 <= R){
            help[i++] = arr[p2++];
        }
        //数组复制
        for (int j = 0; j < help.length; j++) {
            arr[L + j] = help[j];
        }

        return res;
    }
    // for test
    public static int comparator(int[] arr) {
        if (arr == null || arr.length < 2) {
            return 0;
        }
        int res = 0;
        for (int i = 1; i < arr.length; i++) {
            for (int j = 0; j < i; j++) {
                res += arr[j] < arr[i] ? arr[j] : 0;
            }
        }
        return res;
    }

    // for test
    public static int[] generateRandomArray(int maxSize, int maxValue) {
        int[] arr = new int[(int) ((maxSize + 1) * Math.random())];
        for (int i = 0; i < arr.length; i++) {
            arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());
        }
        return arr;
    }

    // for test
    public static int[] copyArray(int[] arr) {
        if (arr == null) {
            return null;
        }
        int[] res = new int[arr.length];
        for (int i = 0; i < arr.length; i++) {
            res[i] = arr[i];
        }
        return res;
    }

    // for test
    public static boolean isEqual(int[] arr1, int[] arr2) {
        if ((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null)) {
            return false;
        }
        if (arr1 == null && arr2 == null) {
            return true;
        }
        if (arr1.length != arr2.length) {
            return false;
        }
        for (int i = 0; i < arr1.length; i++) {
            if (arr1[i] != arr2[i]) {
                return false;
            }
        }
        return true;
    }

    // for test
    public static void printArray(int[] arr) {
        if (arr == null) {
            return;
        }
        for (int i = 0; i < arr.length; i++) {
            System.out.print(arr[i] + " ");
        }
        System.out.println();
    }

    // for test
    public static void main(String[] args) {
        int testTime = 500000;
        int maxSize = 100;
        int maxValue = 100;
        boolean succeed = true;
        for (int i = 0; i < testTime; i++) {
            int[] arr1 = generateRandomArray(maxSize, maxValue);
            int[] arr2 = copyArray(arr1);
            if (getSamllSum(arr1) != comparator(arr2)) {
                succeed = false;
                printArray(arr1);
                printArray(arr2);
                break;
            }
        }
        System.out.println(succeed ? "Nice!" : "Fucking fucked!");
    }
}

第二题

求有多少逆序对(即:求每个数右边有多少个数比它小)

[3,1,0,4,3,1]

逆序对:3,1 3,0 3,1
1,0
4,3 4,1
3,1

代码实现

package com.lzf2.class03;

/**
 * 逆序对问题 (即:求每个数右边有多少个数比它小)
 */
public class ReverPairNumber {

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

    //递归过程
    private static int process(int[] arr, int L, int R) {
        //base case
        if (L == R) {
            return 0;
        }
        //mid
        int mid = L + ((R - L) >> 1);
        //左边求逆序对
        int leftNum = process(arr, L, mid);
        //右边求逆序对
        int rightNum = process(arr, mid + 1, R);
        //合并
        return leftNum + rightNum + merge(arr, L, mid, R);
    }

    private static int merge(int[] arr, int L, int M, int R) {
        int[] help = new int[R - L + 1];
        int i = help.length - 1;
        int p1 = M;
        int p2 = R;
        int res = 0;
        while (p1 >= L && p2 > M) {
            res += arr[p1] > arr[p2] ? (p2 - M) : 0;
            help[i--] = arr[p1] > arr[p2] ? arr[p1--] : arr[p2--];
        }
        while (p1 >= L){
            help[i--] = arr[p1--];
        }
        while (p2 > M){
            help[i--] = arr[p2--];
        }
        for (i = 0; i < help.length; i++) {
            arr[L + i] = help[i];
        }
        return res;
    }



    // for test
    public static int comparator(int[] arr) {
        int ans = 0;
        for (int i = 0; i < arr.length; i++) {
            for (int j = i + 1; j < arr.length; j++) {
                if (arr[i] > arr[j]) {
                    ans++;
                }
            }
        }
        return ans;
    }

    // for test
    public static int[] generateRandomArray(int maxSize, int maxValue) {
        int[] arr = new int[(int) ((maxSize + 1) * Math.random())];
        for (int i = 0; i < arr.length; i++) {
            arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());
        }
        return arr;
    }

    // for test
    public static int[] copyArray(int[] arr) {
        if (arr == null) {
            return null;
        }
        int[] res = new int[arr.length];
        for (int i = 0; i < arr.length; i++) {
            res[i] = arr[i];
        }
        return res;
    }

    // for test
    public static boolean isEqual(int[] arr1, int[] arr2) {
        if ((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null)) {
            return false;
        }
        if (arr1 == null && arr2 == null) {
            return true;
        }
        if (arr1.length != arr2.length) {
            return false;
        }
        for (int i = 0; i < arr1.length; i++) {
            if (arr1[i] != arr2[i]) {
                return false;
            }
        }
        return true;
    }

    // for test
    public static void printArray(int[] arr) {
        if (arr == null) {
            return;
        }
        for (int i = 0; i < arr.length; i++) {
            System.out.print(arr[i] + " ");
        }
        System.out.println();
    }

    // for test
    public static void main(String[] args) {
        int testTime = 500000;
        int maxSize = 100;
        int maxValue = 100;
        System.out.println("测试开始");
        for (int i = 0; i < testTime; i++) {
            int[] arr1 = generateRandomArray(maxSize, maxValue);
            int[] arr2 = copyArray(arr1);
            if (getReverNumber(arr1) != comparator(arr2)) {
                System.out.println("Oops!");
                printArray(arr1);
                printArray(arr2);
                break;
            }
        }
        System.out.println("测试结束");
    }

}

第三题

在一个数组中,

对于每个数num,求有多少个后面的数 * 2 依然<num,求总个数

比如:[3,1,7,0,2]

3的后面有:1,0

1的后面有:0

7的后面有:0,2

0的后面没有

2的后面没有

所以总共有5个

代码实现

package com.lzf2.class03;

/**
 * num的右边有多少个数*2后仍然<num
 */
public class BiggerThanRightTwice {

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

    }

    //递归过程
    private static int process(int[] arr,int L,int R){
        //base case
        if(L == R){
            return 0;
        }
        //mid
        int mid = L + ((R - L) >> 1);
        //左边求出个数
        int leftNum = process(arr,L,mid);
        int rightNum = process(arr,mid + 1,R);
        return leftNum + rightNum + merge(arr,L,mid,R);
    }

    private static int merge(int[] arr,int L,int M,int R){
        //先求出答案
        int ans = 0;
        int windowR = M + 1;
        for (int i = L; i <= M; i++) {
            while (windowR <= R && arr[i] > (arr[windowR]*2)){
                windowR++;
            }
            ans += windowR - M - 1;
        }

        //merge过程
        int[] help = new int[R - L + 1];
        int i = 0;
        int p1 = L;
        int p2 = M + 1;
        while (p1 <= M && p2 <= R){
            help[i++] = arr[p1] <= arr[p2]?arr[p1++]:arr[p2++];
        }
        while (p1 <= M){
            help[i++] = arr[p1++];
        }
        while (p2 <= R){
            help[i++] = arr[p2++];
        }
        for (i = 0; i < help.length; i++) {
            arr[L + i] = help[i];
        }
        return ans;
    }


    // for test
    public static int comparator(int[] arr) {
        int ans = 0;
        for (int i = 0; i < arr.length; i++) {
            for (int j = i + 1; j < arr.length; j++) {
                if (arr[i] > (arr[j] << 1)) {
                    ans++;
                }
            }
        }
        return ans;
    }

    // for test
    public static int[] generateRandomArray(int maxSize, int maxValue) {
        int[] arr = new int[(int) ((maxSize + 1) * Math.random())];
        for (int i = 0; i < arr.length; i++) {
            arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) ((maxValue + 1) * Math.random());
        }
        return arr;
    }

    // for test
    public static int[] copyArray(int[] arr) {
        if (arr == null) {
            return null;
        }
        int[] res = new int[arr.length];
        for (int i = 0; i < arr.length; i++) {
            res[i] = arr[i];
        }
        return res;
    }

    // for test
    public static boolean isEqual(int[] arr1, int[] arr2) {
        if ((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null)) {
            return false;
        }
        if (arr1 == null && arr2 == null) {
            return true;
        }
        if (arr1.length != arr2.length) {
            return false;
        }
        for (int i = 0; i < arr1.length; i++) {
            if (arr1[i] != arr2[i]) {
                return false;
            }
        }
        return true;
    }

    // for test
    public static void printArray(int[] arr) {
        if (arr == null) {
            return;
        }
        for (int i = 0; i < arr.length; i++) {
            System.out.print(arr[i] + " ");
        }
        System.out.println();
    }

    // for test
    public static void main(String[] args) {
        int testTime = 500000;
        int maxSize = 100;
        int maxValue = 100;
        System.out.println("测试开始");
        for (int i = 0; i < testTime; i++) {
            int[] arr1 = generateRandomArray(maxSize, maxValue);
            int[] arr2 = copyArray(arr1);
            if (biggerTwice(arr1) != comparator(arr2)) {
                System.out.println("Oops!");
                printArray(arr1);
                printArray(arr2);
                break;
            }
        }
        System.out.println("测试结束");
    }
}

第四题【困难】

给定一个数组arr,两个整数lower和upper。

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

327. 区间和的个数 - 力扣(LeetCode) (leetcode-cn.com)

暴力破解:O(n3) 暴力+使用前缀和:O(n2)

前置知识:前缀和

//求一个数组arr[i~j]位置的和
//每次去遍历i~j位置的数累加起来比较慢
//arr[i~j]的和相当于:arr[0~j] - arr[0~(i-1)]
//可以先准备好一个前缀和数组.想要0~x的累加和时,直接在前缀和数组中拿就可以
int[] arr = {1,2,4,5,6,7};
int[] sum = new int[arr.length];//前缀和数组
sum[0] = arr[0];
for(int i=1;i<arr.length,i++){
    sum[i] = arr[i]+sum[i-1] 
}

问题转换

1.求出以每一个索引位置为结尾的子数组中,有多少个子数组满足条件。
[1,-2,5,6,-6]
所有子数组:
0~0 
1~1 0~1	
2~2	1~2 0~2 
3~3 2~3 1~3 0~3 
4~4 3~4 2~4 1~4 0~4

2.求某个索引位置x为结尾的子数组中有多少子数组满足条件[lower,upper]
即求的是:0~(x-1)各个位置的前缀和在[y-upper,y-lower]上有多少个?(假设x位置的前缀和为y)
		通俗理解:x位置的这个前缀和在他的左边有多少个前缀和满足[y-upper,y-lower]

3.此时忘掉原数组arr,就看前缀和数组preArr.
就是在求:对于前缀和x,它的左组有多少前缀和落在[x-upper,x-lower]上。(用归并就可求得)

代码(归并实现)

package com.lzf2.class03;

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

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

    private static int process(long[] preSum, int L, int R, int lower, int upper) {
        //base case
        if (L == R) {
            return preSum[L] >= lower && preSum[L] <= upper ? 1 : 0;
        }
        //mid
        int mid = L + ((R - L) >> 1);
        int leftCount = process(preSum, L, mid, lower, upper);
        int rightCount = process(preSum, mid + 1, R, lower, upper);
        return leftCount + rightCount + merge(preSum, L, mid, R, lower, upper);
    }

    private static int merge(long[] preSum, int L, int M, int R, int lower, int upper) {
        //求答案
        int ans = 0;
        //对于右组的某个数,左组中有多少数满足 区间条件
        int windowL = L;
        int windowR = L;
        for (int i = M + 1; i <= R; i++) {
            long min = preSum[i] - upper;
            long max = preSum[i] - lower;
            //[L ... R)左闭右开
            while (windowR <= M && preSum[windowR] <= max){
                windowR++;
            }
            while(windowL <= M && preSum[windowL] < min){
                windowL++;
            }
            ans += windowR - windowL;
        }
        //正常merge
        long[] help = new long[R - L + 1];
        int i = 0;
        int p1 = L;
        int p2 = M + 1;
        while (p1 <= M && p2 <= R){
            help[i++] = preSum[p1] <= preSum[p2] ? preSum[p1++] : preSum[p2++];
        }
        while (p1 <= M){
            help[i++] = preSum[p1++];
        }
        while (p2 <= R){
            help[i++] = preSum[p2++];
        }
        for (i = 0; i < help.length; i++) {
            preSum[L + i] = help[i];
        }

        return ans;
    }

}
上一篇:Java小白入门200例64之Java复制(拷贝)数组的几种方法


下一篇:java 数据结构之数组