
本文详解在原地合并两个有序数组时,为何直接遍历删除零值会导致遗漏,并提供安全、高效的解决方案。
本文详解在原地合并两个有序数组时,为何直接遍历删除零值会导致遗漏,并提供安全、高效的解决方案。
在 LeetCode 第 88 题「合并两个有序数组」中,nums1 是一个长度为 m + n 的数组,前 m 个元素有效,后 n 个位置用 0 占位;nums2 是长度为 n 的独立有序数组。目标是将 nums2 合并入 nums1,最终 nums1 以升序包含全部 m + n 个有效元素——关键点在于:0 仅是占位符,不是待保留的有效数值。
你提供的代码逻辑存在两个核心问题:
-
错误地依赖
i > 0过滤nums2:nums2中可能包含真正的0(例如输入nums2 = [0, 1, 2]),此时if (i > 0)会漏掉有效元素; -
边遍历边
splice导致索引错位:这是导致输出残留首零的根本原因。例如对[0, 0, 0, 1, 2, 2, 3, 5, 6]执行for循环:-
i = 0:删掉索引0的0→ 数组变为[0, 0, 1, 2, 2, 3, 5, 6],长度减 1; -
i = 1:此时原索引1处的元素已变为第二个0(原索引1的0被跳过),继续删除; -
i = 2:当前索引2是1,循环继续……最终仅删掉部分0,首零残留。
-
更严重的是,nums1.sort() 会将所有元素(包括本应丢弃的原始尾部 0)参与排序,破坏了 O(m+n) 时间复杂度预期,也违背“原地合并”的工程意图。
✅ 正确解法应从后向前双指针归并,完全避开 0 处理与重排:
function merge(nums1, m, nums2, n) {
let i = m - 1; // nums1 有效尾部
let j = n - 1; // nums2 尾部
let k = m + n - 1; // 合并后总尾部
// 从后往前归并,大数先就位
while (i >= 0 && j >= 0) {
if (nums1[i] > nums2[j]) {
nums1[k--] = nums1[i--];
} else {
nums1[k--] = nums2[j--];
}
}
// 若 nums2 还有剩余,复制过去(nums1 剩余无需处理,已在原位)
while (j >= 0) {
nums1[k--] = nums2[j--];
}
// 注意:nums1 剩余元素天然有序且位置正确,无需搬运
}? 为什么此方案安全?
- 完全不依赖
0的值判断,而是严格按有效长度m和n操作; - 归并过程只写入
k位置,覆盖原有占位0,无需额外删除; - 时间复杂度
O(m + n),空间复杂度O(1),符合题目约束。
⚠️ 注意事项:
- 切勿使用
sort()—— 它会打乱原始有序结构,且无法区分占位0与真实0; - 若坚持用
filter+sort(仅作思路验证),必须明确过滤条件:nums1.filter(x => x !== 0).concat(nums2).sort((a,b) => a-b),但这违反“in-place”要求且效率低下; - 实际面试/工程中,双指针向后归并是标准最优解,务必掌握其边界处理逻辑。
通过理解索引动态变化的本质,选择合适的数据操作范式,才能写出鲁棒、高效、符合题意的合并代码。

















