在数据处理和算法设计中,有序数组合并是一个基础而重要的问题。本文将带你深入剖析 LeetCode 经典题目「合并两个有序数组」,逐步优化解法。
问题描述
给定两个按 非递减顺序 排列的整数数组 nums1
和 nums2
:
nums1
长度为m + n
,前m
个元素有效,后n
个元素为占位值 0nums2
长度为n
- 需将
nums2
合并到nums1
中,使合并后数组保持 非递减顺序 - 要求 :直接修改
nums1
,不返回新数组
css
输入:nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3
输出:[1,2,2,3,5,6]
解法一:暴力合并后排序
核心思想:忽略数组有序特性,直接合并后排序。
算法步骤:
- 将
nums2
复制到nums1
尾部 - 调用内置排序函数排序
javascript
const merge = (nums1, m, nums2, n) => {
// 复制 nums2 到 nums1 尾部
for (let i = 0; i < n; i++) {
nums1[m + i] = nums2[i];
}
// 原地排序
nums1.sort((a, b) => a - b);
};
时间复杂度分析:
- 复制操作:O(n)
- 排序操作:O((m+n)log(m+n))
- 总时间复杂度:O((m+n)log(m+n))
空间复杂度:O(1)(原地排序)
缺点:未利用数组有序特性,排序开销大
解法二:正向双指针(额外空间)
核心思想:利用双指针遍历有序数组,使用临时数组存储结果
算法步骤:
- 创建临时数组
temp
- 初始化双指针
p1=0
(nums1),p2=0
(nums2) - 比较指针值,取较小值放入
temp
- 处理剩余元素
- 复制
temp
回nums1
javascript
const merge = (nums1, m, nums2, n) => {
// 创建临时数组
const temp = new Array(m + n);
let p1 = 0, p2 = 0, t = 0;
// 合并有序部分
while (p1 < m && p2 < n) {
if (nums1[p1] <= nums2[p2]) {
temp[t++] = nums1[p1++];
} else {
temp[t++] = nums2[p2++];
}
}
// 处理 nums1 剩余
while (p1 < m) temp[t++] = nums1[p1++];
// 处理 nums2 剩余
while (p2 < n) temp[t++] = nums2[p2++];
// 复制回 nums1
for (let i = 0; i < m + n; i++) {
nums1[i] = temp[i];
}
};
时间复杂度:O(m+n)(遍历所有元素一次)
空间复杂度:O(m+n)(需要额外空间)
缺点:空间复杂度不满足最优要求
知识点扩展:双指针技巧 双指针是处理有序数组的高效技巧,常见模式:
- 同向指针:解决滑动窗口问题
- 相向指针:解决两数之和等问题
- 快慢指针:解决链表环检测问题
解法三:逆向双指针(最优解)
核心思想 :利用 nums1
尾部空闲空间,从后向前填充较大值
算法步骤:
- 初始化三指针:
p1 = m-1
(nums1 有效末尾)p2 = n-1
(nums2 末尾)p = m+n-1
(合并位置)
- 从后向前比较并填充
- 处理 nums2 剩余元素
javascript
const merge = (nums1, m, nums2, n) => {
let p1 = m - 1; // nums1 有效部分末尾
let p2 = n - 1; // nums2 末尾
let p = m + n - 1; // 合并位置
// 从后向前比较并填充
while (p1 >= 0 && p2 >= 0) {
if (nums1[p1] > nums2[p2]) {
nums1[p--] = nums1[p1--];
} else {
nums1[p--] = nums2[p2--];
}
}
// 处理 nums2 剩余元素
while (p2 >= 0) {
nums1[p--] = nums2[p2--];
}
};
时间复杂度:O(m+n)(每个元素仅处理一次)
空间复杂度:O(1)(完全原地操作)
关键点解析:
- 逆向操作:避免覆盖未比较元素
- 尾部空闲空间:利用 nums1 的预留空间
- 剩余处理:只需处理 nums2 剩余元素
为什么不需要处理 nums1 剩余?
- 当 nums2 元素处理完后,nums1 剩余元素已处于正确位置
- 这些元素是全局最小值且位置正确
算法比较总结
解法 | 时间复杂度 | 空间复杂度 | 核心优势 | 适用场景 |
---|---|---|---|---|
暴力排序 | O((m+n)log(m+n)) | O(1) | 实现简单 | 快速原型开发 |
正向双指针 | O(m+n) | O(m+n) | 利用有序特性 | 无空间限制场景 |
逆向双指针 | O(m+n) | O(1) | 原地操作,空间最优 | 内存敏感环境 |
合并有序数组看似简单,却蕴含着分治思想、双指针技巧和空间优化策略。掌握这一基础算法,将为处理更复杂的有序数据问题奠定坚实基础。