题目
给你两个按 非递减顺序 排列的整数数组 nums1
和 nums2
,另有两个整数 m
和 n
,分别表示 nums1
和 nums2
中的元素数目。
请你合并nums2
到nums1
中,使合并后的数组同样按 非递减顺序 排列。
注意: 最终,合并后数组不应由函数返回,而是存储在数组 nums1
中。为了应对这种情况,nums1
的初始长度为 m + n
,其中前 m
个元素表示应合并的元素,后 n
个元素为 0
,应忽略。nums2
的长度为 n
。
示例 1:
输入: nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3
输出: [1,2,2,3,5,6]
解释: 需要合并 [1,2,3] 和 [2,5,6] 。
合并结果是 [1,2,2,3,5,6] ,其中斜体加粗标注的为 nums1 中的元素。
示例 2:
输入: nums1 = [1], m = 1, nums2 = [], n = 0
输出: [1]
解释: 需要合并 [1] 和 [] 。
合并结果是 [1] 。
示例 3:
输入: nums1 = [0], m = 0, nums2 = [1], n = 1
输出: [1]
解释: 需要合并的数组是 [] 和 [1] 。
合并结果是 [1] 。
注意,因为 m = 0 ,所以 nums1 中没有元素。nums1 中仅存的 0 仅仅是为了确保合并结果可以顺利存放到 nums1 中。
方法一:直接合并后排序
splice()
array.splice(start, deleteCount, item1, item2, ...)
参数
start
:表示开始删除或添加的索引位置。如果是负数,则从数组末尾开始计算位置。deleteCount
:表示要删除的元素个数。如果为 0,则不删除元素。item1, item2, ...
:要添加到数组中的新元素(可选)。如果没有提供,则splice
只删除元素。
返回值
- 返回一个包含被删除元素的新数组。如果没有删除元素,则返回一个空数组。
sort()
array.sort([compareFunction])
参数
compareFunction
(可选):用于定义数组元素的排序顺序。如果不提供,数组元素将按字典顺序排序。compareFunction
应该返回一个数值:- 如果
compareFunction(a, b)
小于 0,a
会被排列到b
之前。 - 如果
compareFunction(a, b)
等于 0,a
和b
的顺序保持不变。 - 如果
compareFunction(a, b)
大于 0,a
会被排列到b
之后。
let arr = [40, 100, 1, 5, 25, 10]; arr.sort((a, b) => a - b); //a-b=60>0,a会被排列到b之后,升序 console.log(arr); // [1, 5, 10, 25, 40, 100] arr.sort((a, b) => b - a); //b-a=-60<0,a会被排列到b之前,降序 console.log(arr); // [100, 40, 25, 10, 5, 1]
- 如果
返回值
- 返回排序后的数组。
最终答案
var merge = function(nums1, m, nums2, n) {
nums1.splice(m, nums1.length - m, ...nums2);
nums1.sort((a, b) => a - b);
};
方法二:双指针
方法一没有利用数组 nums1
与 nums2
已经被排序的性质。为了利用这一性质,我们可以使用双指针
方法。这一方法将两个数组看作队列,每次从两个数组头部取出比较小的数字放到结果中。
如下面的动画所示:
var merge = function(nums1, m, nums2, n) {
let p1 = 0, p2 = 0;
const sorted = new Array(m + n).fill(0);
var cur;
while (p1 < m || p2 < n) {
if (p1 === m) {
cur = nums2[p2++];
} else if (p2 === n) {
cur = nums1[p1++];
} else if (nums1[p1] < nums2[p2]) {
cur = nums1[p1++];
} else {
cur = nums2[p2++];
}
sorted[p1 + p2 - 1] = cur;
}
for (let i = 0; i < m + n; i++) {
nums1[i] = sorted[i];
}
};
方法三:逆向双指针
方法二中,之所以要使用临时变量,是因为如果直接合并到数组nums1
中,担心nums1
中的元素可能会在取出之前被覆盖。实际上,把 nums1
的数字移到另一个空位,又产生了一个新的空位,空位个数不变
,所以总是有空位可以让 nums2
的数字填入,不会出现覆盖的情况。因此可以指针设置为从后向前遍历
,每次取两者之中的较大者
放进 nums1
的最后面。
var merge = function(nums1, m, nums2, n) {
let p1 = m - 1; // 指向nums1的最后一个有效元素
let p2 = n - 1; // 指向nums2的最后一个元素
let tail = m + n - 1; // 指向合并后nums1的最后一个位置
// 当nums2还有剩余元素时
while (p2 >= 0) {
// 如果nums1中还有元素并且nums1[p1] > nums2[p2]
if (p1 >= 0 && nums1[p1] > nums2[p2]) {
nums1[tail--] = nums1[p1--]; // 将nums1[p1]填入nums1的尾部,并移动指针
} else {
nums1[tail--] = nums2[p2--]; // 否则将nums2[p2]填入nums1的尾部,并移动指针
}
}
};
代码解释
-
初始化指针:
p1
指向nums1
中有效元素的最后一个位置,即m - 1
。p2
指向nums2
的最后一个位置,即n - 1
。tail
指向合并后的nums1
的最后一个位置,即m + n - 1
。
-
合并数组:
- 使用
while
循环,当p2
大于等于 0 时继续循环。 - 在每次循环中,比较
nums1[p1]
和nums2[p2]
。- 如果
p1
大于等于 0 且nums1[p1]
大于nums2[p2]
,则将nums1[p1]
填入nums1[tail]
,并将p1
和tail
向前移动。 - 否则,将
nums2[p2]
填入nums1[tail]
,并将p2
和tail
向前移动。
- 如果
- 使用
-
处理剩余元素:
- 因为循环条件是
p2 >= 0
,当p2
小于 0 时,循环结束。 - 如果
nums1
中还有剩余元素,由于它们已经在正确的位置上,所以不需要额外处理。
- 因为循环条件是
优点
- 简洁:代码简短,逻辑清晰。
- 高效:只使用一个
while
循环进行合并,时间复杂度为 O(m + n)。 - 就地操作:直接在
nums1
中进行合并,不需要额外的空间。