算法练习-合并两个有序数组
原题链接-合并两个有序数组
题目核心要求
-
有两个已经排好序(非递减) 的数组
nums1
和nums2
。 -
要把
nums2
合并到nums1
里面去。 -
不能新开一个数组来存结果,必须直接修改
nums1
。 -
nums1
的长度足够大,后面用0
占好了位置。
方法一:直觉法(先合并,再排序)
这是最容易想到的方法,但不是最高效的),不过非常适合理解。
思路:
-
直接把
nums2
里的所有元素,按顺序塞到nums1
末尾那些0
的位置上。 -
然后对整个
nums1
调用 Python 自带的排序函数sort()
。
Python 代码:
def merge(nums1, m, nums2, n):""":type nums1: List[int]:type m: int:type nums2: List[int]:type n: int:rtype: None Do not return anything, modify nums1 in-place instead."""# 1. 把 nums2 的所有元素,从 nums1 的第 m 个位置开始覆盖# 例如:m=3, 就从 nums1[3] 开始覆盖后面的0nums1[m:m+n] = nums2# 2. 直接对 nums1 进行原地排序nums1.sort()# 测试一下
nums1 = [1,2,3,0,0,0]
m = 3
nums2 = [2,5,6]
n = 3merge(nums1, m, nums2, n)
print(nums1) # 输出 [1, 2, 2, 3, 5, 6]
缺点:
我们没有利用“两个数组都已经排好序”这个条件,直接排序的时间复杂度是 O((m+n) * log(m+n))
,不够高效。
方法二:双指针法(从后往前 - 推荐!)
这是最优解。它的核心思想是利用已知的“有序”条件,像拉链一样把两个数组合并起来。
思路比喻:
假设你有两叠已经从大到小排好序的卡片(nums1
的有效部分和 nums2
),现在你要把它们合并成一叠,并且只能从最上面(最大的数字)开始比较和放置。
-
设置三个“指针”:
-
p1
:指向nums1
有效部分的最后一个元素(索引m-1
)。 -
p2
:指向nums2
的最后一个元素(索引n-1
)。 -
p
:指向nums1
数组的最后一个位置(索引m+n-1
),这是用来放最终结果的。
-
-
从后往前比较
nums1[p1]
和nums2[p2]
:-
谁大,就把谁放到
nums1[p]
的位置。 -
然后,对应的指针 (
p1
或p2
) 和p
都向前移动一位。
-
-
重复步骤2,直到其中一个数组的所有元素都比较完。
-
重要收尾:如果最后
nums2
里还有剩余的元素(即p2 >= 0
),说明这些元素都是最小的,需要把它们按顺序复制到nums1
最前面的位置。
为什么要从后往前?
因为 nums1
的后面是空闲的 0
,从大到小放置不会覆盖掉 nums1
前面还没比较到的有效数字。如果从前往后放,会覆盖数据,需要额外空间。
Python 代码:
def merge(nums1, m, nums2, n):# 初始化三个指针p1 = m - 1 # 指向 nums1 有效部分的末尾p2 = n - 1 # 指向 nums2 的末尾p = m + n - 1 # 指向 nums1 整个数组的末尾# 当两个数组都还有元素没比较时while p1 >= 0 and p2 >= 0:if nums1[p1] > nums2[p2]:# 如果 nums1 的元素大,把它放到后面nums1[p] = nums1[p1]p1 -= 1else:# 如果 nums2 的元素大(或相等),把它放到后面nums1[p] = nums2[p2]p2 -= 1p -= 1 # 每放一个元素,总指针前移一位# 收尾:如果 nums2 还有剩余元素(意味着这些是最小的)# 直接把它们复制到 nums1 的前面# 如果 p2 < 0,说明 nums2 已经全部处理完,这个循环不会执行nums1[:p2 + 1] = nums2[:p2 + 1]# 测试
nums1 = [1,2,3,0,0,0]
m = 3
nums2 = [2,5,6]
n = 3merge(nums1, m, nums2, n)
print(nums1) # 输出 [1, 2, 2, 3, 5, 6]
为什么最后要收尾?
来看一个例子:nums1 = [4, 5, 6, 0, 0, 0]
, nums2 = [1, 2, 3]
-
按照从后往前比较:
-
6>3
-> 放6 -
5>3
-> 放5 -
4>3
-> 放4
-
-
此时
p1
已经变成-1
,循环结束。但nums2
里还剩[1, 2, 3]
这三个元素没处理! -
所以我们需要
nums1[:p2 + 1] = nums2[:p2 + 1]
这行代码,把nums2
剩下的所有元素直接放到nums1
的最开头。
复杂度分析:
-
时间复杂度:O(m+n)。我们只遍历了每个元素一次。
-
空间复杂度:O(1)。没有使用额外的数组空间,只用了几个变量。
总结
方法 | 思路 | 优点 | 缺点 | 推荐度 |
---|---|---|---|---|
先合并后排序 | 直接把 | 代码极其简单,容易想到 | 没有利用有序条件,效率低 | ⭐⭐ |
双指针(从后往前) | 像拉链一样,从最大的元素开始比较和放置 | 效率最高,利用了有序条件 | 思路稍微复杂一点 | ⭐⭐⭐⭐⭐ |
我建议:
-
先理解方法一的直觉思路。
-
再重点攻克方法二的双指针法,这是算法的核心思想,在很多其他问题中也会用到。
-
多画图模拟一下指针移动的过程,就会豁然开朗!