一道合并数组算法题引发的血案

209 阅读2分钟

面试官:你平时有什么爱好吗?
我:看书刷LeetCode(大多数问题都要看题解)
面试官:那你刷过合并有序数组那道题吗?
我:刷过,有两个方法:一是定义一个大数组,用双层循环从前往后遍历两个数组。二是题解的方法,将从前往后遍历改为了从后往前遍历,可以不用辅助空间。
面试官:那给你10分钟,写个合并n个有序数组的代码。
(打开了腾讯会议的分屏和VSCode,哼哧哼哧10分钟过去了,还没写出来)
面试官:今天的面试到这儿了,你回去等通知吧。

这是我今年面试一家在线教育公司的亲身经历。合并有序数组属于较为基础的题目,但是由于平时疏于训练,临时思路卡壳。后面面试结束后,又花了20多分钟才完成。记录一下,分享给大家,欢迎大家一起探讨。

LeetCode:合并有序数组

给定两个排序后的数组 AB,其中 A 的末端有足够的缓冲空间容纳 B。 编写一个方法,将 B 合并入 A 并排序。

初始化 AB 的元素数量分别为 mn。

示例:

输入:
A = [1,2,3,0,0,0], m = 3
B = [2,5,6],       n = 3

输出: [1,2,2,3,5,6]
说明:

A.length == n + m

看到题目常规想法是直接用两个循环,时间复杂度为O(n^2),空间复杂度为O(m+n)。题解没有对于数组是顺序还是逆序做判断,应该是有序默认为从小到大排序。

public class Solution {
    public static int[] merge(int[] A, int[] B, int m, int n){
        int[] temp = new int[m + n];
        int i = 0, j = 0, cursor = 0;
        while(i < m && j < n){
            if(A[i] > B[j]){
                temp[cursor] = B[j];
                j++;
            }else{
                temp[cursor] = A[i];
                i++;
            }
            cursor++;
        }
        while(i < m){
            temp[cursor] = A[i];
            i++;
            cursor++;
        }
        while(j < n){
            temp[cursor] = B[j];
            j++;
            cursor++;
        }
        return temp;
    }

    public static void main(String args[]){
       int[] A = {2,5,7,0,0,0};
       int[] B = {3,4,6};
       System.out.println(Arrays.toString(Solution.merge(A, B, 3, 3)));
    }
}

// output: [2, 3, 4, 5, 6, 7]

注意到题目中提到了A 的末端有足够的缓冲空间容纳 B,因此可以用逆向双指针来做。时间复杂度为O(n^2),空间复杂度为O(1)。

public class Learn {
    public static int[] mergeTwo(int[] A, int[] B, int m, int n) {
        // int[] temp = new int[m + n];
        int pa = m - 1;
        int pb = n - 1;
        int tail = m + n - 1;
        while(pa >= 0 || pb >= 0){
            if(pa == -1){
                A[tail] = B[pb];
                pb--;
            }
            else if(pb == -1){
                A[tail] = A[pa];
                pa--;
            }
            else if(A[pa] > B[pb]){
                A[tail] = A[pa];
                pa--;
            }else{
                A[tail] = B[pb];
                pb--;
            }
            tail--;
        }
        // return temp;
        return A;
    }

    public static void main(String args[]){
        int[] A = {2,5,7,0,0,0};
       int[] B = {3,4,6};
       System.out.println(Arrays.toString(Learn.mergeTwo(A, B, 3, 3)));
    }
}

面试官出的合并n个数组的题目,只要将逆向双指针稍微修改一下即可,代码如下:

public class Learn {

    public static int[] merge(int[][] arrs){
        int[] temp = arrs[0];
        for(int i=1;i<arrs.length;i++){
            temp=Learn.mergeTwo(temp, arrs[i]);
        }
        return temp;
    }

    public static int[] mergeTwo(int[] A, int[] B) {
        int[] temp = new int[A.length + B.length];
        int pa = A.length - 1;
        int pb = B.length - 1;
        int tail = A.length + B.length - 1;
        while(pa >= 0 || pb >= 0){
            if(pa == -1){
                temp[tail] = B[pb];
                pb--;
            }
            else if(pb == -1){
                temp[tail] = A[pa];
                pa--;
            }
            else if(A[pa] > B[pb]){
                temp[tail] = A[pa];
                pa--;
            }else{
                temp[tail] = B[pb];
                pb--;
            }
            tail--;
        }
        return temp;
    }

    public static void main(String args[]){
       int[][] arrs = {{1,4,7},{2,5,8},{3,7,9}};
       System.out.println(Arrays.toString(Learn.merge(arrs)));
    }
}

当时没想到这样一个和两个合并来做,想着使用归并算法或者多线程来做,可惜代码不熟。下一篇博客会考虑使用归并和多线程来做合并数组,并测试性能上是否有提升。