Nameless Site

But one day, you will stand before its decrepit gate,without really knowing why.

0%

合并两个有序数组

来源Leetcode第88题合并两个有序数组

给定两个有序整数数组 nums1 和 nums2,将 nums2 合并到 nums1 中,使得 num1 成为一个有序数组。

说明:

  • 初始化 nums1 和 nums2 的元素数量分别为 m 和 n。
  • 你可以假设 nums1 有足够的空间(空间大小大于或等于 m + n)来保存 nums2 中的元素。
    示例:

输入:
nums1 = [1,2,3,0,0,0], m = 3
nums2 = [2,5,6], n = 3

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

sort

最简单的想法就是将nums2数组的元素合并到nums1里,这个只用一个for循环就可以实现,最后调用系统库函数sort就可以了。
然而我万万没想到的是,系统库函数里有个arraycopy,甚至可以不用复制数组,我傻了。

源码:public static native void arraycopy(Object src, int srcPos, Object dest, int destPos,int length);

参数:

  • src:要复制的数组(源数组)

  • srcPos:复制源数组的起始位置

  • dest:目标数组

  • destPos:目标数组的下标位置

  • length:要复制的长度

代码如下:

1
2
3
4
5
6
class Solution {
public void merge(int[] nums1, int m, int[] nums2, int n) {
System.arraycopy(nums2, 0, nums1, m, n);
Arrays.sort(nums1);
}
}

官方题解采用了两种双指针的解法,分别是从前往后和从后往前。

从前往后

一般而言,对于有序数组可以通过 双指针法 达到OO(n+m)的时间复杂度。最直接的算法实现是将指针p1置为nums1的开头, p2为 nums2的开头,在每一步将最小值放入输出数组中。由于nums1 是用于输出的数组,需要将nums1中的前m个元素放在其他地方,也就需要O(m)的空间复杂度。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Solution {
public void merge(int[] nums1, int m, int[] nums2, int n) {
// Make a copy of nums1.
int [] nums1_copy = new int[m];
System.arraycopy(nums1, 0, nums1_copy, 0, m);

// Two get pointers for nums1_copy and nums2.
int p1 = 0;
int p2 = 0;

// Set pointer for nums1
int p = 0;

// Compare elements from nums1_copy and nums2
// and add the smallest one into nums1.
while ((p1 < m) && (p2 < n))
nums1[p++] = (nums1_copy[p1] < nums2[p2]) ? nums1_copy[p1++] : nums2[p2++];

// if there are still elements to add
if (p1 < m)
System.arraycopy(nums1_copy, p1, nums1, p1 + p2, m + n - p1 - p2);
if (p2 < n)
System.arraycopy(nums2, p2, nums1, p1 + p2, m + n - p1 - p2);
}
}

从后往前

从前往后已经取得了最优的时间复杂度O(n+m),但需要使用额外空间。这是由于在从头改变nums1的值时,需要把nums1中的元素存放在其他位置。
如果我们从结尾开始改写 nums1 的值又会如何呢?这里没有信息,因此不需要额外空间。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
public void merge(int[] nums1, int m, int[] nums2, int n) {
// two get pointers for nums1 and nums2
int p1 = m - 1;
int p2 = n - 1;
// set pointer for nums1
int p = m + n - 1;

// while there are still elements to compare
while ((p1 >= 0) && (p2 >= 0))
// compare two elements from nums1 and nums2
// and add the largest one in nums1
nums1[p--] = (nums1[p1] < nums2[p2]) ? nums2[p2--] : nums1[p1--];

// add missing elements from nums2
//当退出循环时,有可能是p1为负,也有可能是p2为负
//如果是p2为负,则说明复制已经完成,这时候的复制长度p2 + 1为0
//如果是p1为负,说明nums1数组的元素大于nums2数组的元素,这时候nums1数组前面空了p2 + 1个元素出来,因而要复制nums2数组的p2 + 1个元素到nums1
System.arraycopy(nums2, 0, nums1, 0, p2 + 1);
}
}