在算法面试和日常开发中,合并两个有序数组是非常经典的基础算法题,它不仅考察对数组的操作能力,还能体现对「双指针」思想的理解,是必须掌握的入门级算法。
今天我就用最通俗易懂的方式,带大家彻底搞懂这道题,包含思路分析、图解、多语言代码实现,新手也能轻松学会。
一、题目描述
给你两个非递减顺序排列的整数数组
nums1 和 nums2,另有两个整数 m 和 n,分别表示 nums1 和 nums2 中的元素数目。请你合并
nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。注意:
- 最终,合并后数组不应由函数返回,而是存储在数组
nums1中。 nums1的初始长度为m + n,其中前m个元素表示应合并的元素,后n个元素为 0,应忽略。
示例:
plaintext
输入:nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3
输出:[1,2,2,5,6,3]
二、解题思路
思路 1:暴力合并后排序(简单但不推荐)
最容易想到的方法:
- 把
nums2的元素直接拷贝到nums1的空位上; - 对整个
nums1执行排序。
✅ 优点:代码简单,新手易写
❌ 缺点:时间复杂度高(),没有利用「两个数组原本有序」的条件,面试中会被扣分。
思路 2:双指针正向遍历(推荐基础版)
利用两个数组已有序的特点:
- 创建一个临时数组存储结果;
- 用两个指针分别指向
nums1和nums2的起始位置; - 比较两个指针指向的元素,将较小的放入临时数组;
- 遍历完成后,将剩余元素直接追加到临时数组;
- 最后将临时数组拷贝回
nums1。
✅ 优点:时间复杂度 ,效率高
✅ 缺点:需要额外开辟 的空间
思路 3:双指针逆向遍历(最优解)
这是面试标准答案,不占用额外空间:
- 三个指针:
p指向nums1末尾(最终存放位置),p1指向nums1有效元素末尾,p2指向nums2末尾; - 从后往前比较元素,大的元素直接放入
nums1的p位置; - 指针向前移动,直到遍历完所有元素。
✅ 优点:时间复杂度 ,空间复杂度 ,原地修改
✅ 推荐:面试、笔试首选写法
三、算法图解(逆向双指针)
以示例
nums1 = [1,2,3,0,0,0],nums2 = [2,5,6] 为例:- 初始指针:p=5,p1=2,p2=2
- 比较 3 和 6 → 6 更大,放入 p=5,p2=1,p=4
- 比较 3 和 5 → 5 更大,放入 p=4,p2=0,p=3
- 比较 3 和 2 → 3 更大,放入 p=3,p1=1,p=2
- 依次遍历,最终得到有序数组
全程无需额外数组,直接在
nums1 上操作,空间利用率拉满。四、通用代码实现
1. Python 代码(博客通用格式)
python
运行
def merge(nums1, m, nums2, n):
"""
合并两个有序数组(最优解:逆向双指针)
:param nums1: 有序数组1,长度 m+n,后n位为0
:param m: nums1有效元素个数
:param nums2: 有序数组2
:param n: nums2有效元素个数
"""
# 定义三个指针
p = m + n - 1 # nums1总长度最后一位
p1 = m - 1 # nums1有效元素最后一位
p2 = n - 1 # nums2最后一位
# 从后往前遍历
while p1 >= 0 and p2 >= 0:
if nums1[p1] > nums2[p2]:
nums1[p] = nums1[p1]
p1 -= 1
else:
nums1[p] = nums2[p2]
p2 -= 1
p -= 1
# 如果nums2还有剩余元素,直接拷贝
nums1[:p2+1] = nums2[:p2+1]
# 测试代码
if __name__ == "__main__":
nums1 = [1,2,3,0,0,0]
m = 3
nums2 = [2,5,6]
n = 3
merge(nums1, m, nums2, n)
print("合并结果:", nums1) # 输出:[1,2,2,5,6,3]
2. Java 代码(博客通用格式)
java
运行
public class MergeSortedArray {
public void merge(int[] nums1, int m, int[] nums2, int n) {
int p = m + n - 1;
int p1 = m - 1;
int p2 = n - 1;
while (p1 >= 0 && p2 >= 0) {
if (nums1[p1] > nums2[p2]) {
nums1[p] = nums1[p1--];
} else {
nums1[p] = nums2[p2--];
}
p--;
}
// 拷贝nums2剩余元素
System.arraycopy(nums2, 0, nums1, 0, p2 + 1);
}
// 测试
public static void main(String[] args) {
int[] nums1 = {1,2,3,0,0,0};
int[] nums2 = {2,5,6};
new MergeSortedArray().merge(nums1, 3, nums2, 3);
for (int num : nums1) {
System.out.print(num + " ");
}
}
}
3. JavaScript 代码(博客通用格式)
javascript
运行
var merge = function(nums1, m, nums2, n) {
let p = m + n - 1;
let p1 = m - 1;
let p2 = n - 1;
while (p1 >= 0 && p2 >= 0) {
if (nums1[p1] > nums2[p2]) {
nums1[p] = nums1[p1--];
} else {
nums1[p] = nums2[p2--];
}
p--;
}
// 处理剩余元素
while (p2 >= 0) {
nums1[p--] = nums2[p2--];
}
};
// 测试
let nums1 = [1,2,3,0,0,0];
let nums2 = [2,5,6];
merge(nums1, 3, nums2, 3);
console.log(nums1);
五、复杂度分析
- 时间复杂度:,只需遍历两个数组一次
- 空间复杂度:,原地修改数组,无额外空间开销
六、总结
- 合并两个有序数组是算法入门必考题,核心思想是双指针;
- 暴力排序法简单但低效,逆向双指针是最优解;
- 从后往前遍历可以避免元素覆盖,无需额外空间;
- 代码逻辑清晰,适合面试手写,通过率 100%。
我是编程学习者,持续分享算法入门、编程实战、面试干货,如果你觉得这篇文章对你有帮助,欢迎点赞、收藏、转发~我们下期再见!
关键点回顾
- 核心解法:逆向双指针,时间 ,空间
- 关键操作:从后往前比较,大元素后置,避免覆盖
- 适用场景:算法面试、笔试、数据结构基础练习