面试官:你平时有什么爱好吗?
我:看书刷LeetCode(大多数问题都要看题解)
面试官:那你刷过合并有序数组那道题吗?
我:刷过,有两个方法:一是定义一个大数组,用双层循环从前往后遍历两个数组。二是题解的方法,将从前往后遍历改为了从后往前遍历,可以不用辅助空间。
面试官:那给你10分钟,写个合并n个有序数组的代码。
(打开了腾讯会议的分屏和VSCode,哼哧哼哧10分钟过去了,还没写出来)
面试官:今天的面试到这儿了,你回去等通知吧。
这是我今年面试一家在线教育公司的亲身经历。合并有序数组属于较为基础的题目,但是由于平时疏于训练,临时思路卡壳。后面面试结束后,又花了20多分钟才完成。记录一下,分享给大家,欢迎大家一起探讨。
LeetCode:合并有序数组
给定两个排序后的数组 A 和 B,其中 A 的末端有足够的缓冲空间容纳 B。 编写一个方法,将 B 合并入 A 并排序。
初始化 A 和 B 的元素数量分别为 m 和 n。
示例:
输入:
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)));
}
}
当时没想到这样一个和两个合并来做,想着使用归并算法或者多线程来做,可惜代码不熟。下一篇博客会考虑使用归并和多线程来做合并数组,并测试性能上是否有提升。