ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

归并排序+面试题

2021-09-21 18:30:43  阅读:126  来源: 互联网

标签:面试题 return arr int 归并 ++ length arr1 排序


归并排序

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;
    }

}

标签:面试题,return,arr,int,归并,++,length,arr1,排序
来源: https://blog.csdn.net/weixin_47254987/article/details/120404023

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有