合并两个有序数组
你有两个按非递减顺序排列的整数数组:
nums1
,长度为m + n
,其中前m
个元素是有效的数字,后面的n
个元素为0
,需要忽略。nums2
,长度为n
,包含了n
个有效的数字。
你的任务是将 nums2
中的元素合并到 nums1
中,使得合并后的数组仍然按非递减顺序排列。
注意:
- 你不需要返回新的数组,而是直接修改
nums1
数组,使其包含合并后的结果。 nums1
有足够的空间(长度为m + n
)来容纳来自nums2
的元素。
输入参数
nums1: number[]
- 第一个数组,长度为m + n
。m: number
-nums1
中有效元素的数量。nums2: number[]
- 第二个数组,长度为n
。n: number
-nums2
中元素的数量。
任务目标
将 nums2
中的元素合并到 nums1
中,使得 nums1
按非递减顺序排列。
示例
示例 1:
输入:
nums1 = [1,2,3,0,0,0], m = 3
nums2 = [2,5,6], n = 3输出:
nums1 = [1,2,2,3,5,6]
解释:
- 初始
nums1
的前m=3
个元素是[1,2,3]
,后面有三个0
,可用于填充。 nums2
是[2,5,6]
。- 合并后,
nums1
变为[1,2,2,3,5,6]
。
解决思路
为了在不使用额外空间的情况下合并两个数组,我们可以从两个数组的末尾开始比较和插入。这种方法称为 逆向双指针法。
步骤:
-
初始化指针:
i = m - 1
:指向nums1
中最后一个有效元素。j = n - 1
:指向nums2
中最后一个元素。k = m + n - 1
:指向nums1
的末尾位置。
-
逆向遍历:
- 比较
nums1[i]
和nums2[j]
。 - 将较大的值放入
nums1[k]
,然后移动相应的指针和k
。
- 比较
-
处理剩余元素:
- 如果
nums2
中还有剩余元素(j >= 0
),需要将它们全部复制到nums1
中。 - 如果
nums1
中还有剩余元素(i >= 0
),不需要处理,因为它们已经在正确的位置上。
- 如果
代码详解
function merge(nums1: number[], m: number, nums2: number[], n: number): void {let i = m - 1; // nums1 的有效元素的最后一个索引let j = n - 1; // nums2 的最后一个索引let k = m + n - 1; // nums1 的最后一个位置索引// 从后往前遍历,比较并插入较大的元素while (i >= 0 && j >= 0) {if (nums1[i] > nums2[j]) {nums1[k] = nums1[i]; // 将较大的 nums1[i] 放到 nums1[k]i--; // 移动 nums1 的指针} else {nums1[k] = nums2[j]; // 将较大的 nums2[j] 放到 nums1[k]j--; // 移动 nums2 的指针}k--; // 移动合并后数组的指针}// 如果 nums2 中还有剩余元素,复制到 nums1while (j >= 0) {nums1[k] = nums2[j];j--;k--;}// 不需要处理 nums1 中剩余的元素,它们已经在正确的位置上
}
解释:
-
逆向遍历的原因:因为
nums1
有足够的空间,如果我们从前往后插入元素,会覆盖掉nums1
中还未处理的元素。逆向遍历可以避免这个问题。 -
比较大小并插入:每次比较
nums1[i]
和nums2[j]
,将较大的放到nums1[k]
,这样可以保证从后往前构建有序的数组。 -
处理剩余的
nums2
元素:如果nums2
还有元素未处理完(j >= 0
),需要继续复制到nums1
中。 -
不需要处理剩余的
nums1
元素:因为它们已经在nums1
的正确位置上,无需移动。
时间和空间复杂度
- 时间复杂度:O(m + n),需要遍历两个数组的所有元素。
- 空间复杂度:O(1),只使用了常数级别的额外空间。