题目
今天的题目是:合并两个有序数组
给你两个有序整数数组 nums1 和 nums2,请你将 nums2 合并到 nums1 中,使 nums1 成为一个有序数组。
说明:
- 初始化 nums1 和 nums2 的元素数量分别为 m 和 n 。
- 你可以假设 nums1 有足够的空间(空间大小大于或等于 m + n)来保存 nums2 中的元素。 示例:
输入:
nums1 = [1,2,3,0,0,0], m = 3
nums2 = [2,5,6], n = 3
输出:[1,2,2,3,5,6]
解题思路
首先说一个最耍赖,但是背后隐藏着坑的一个解法:直接用python的内置函数
在python中,可以使用内置的全局sorted()方法对可迭代的序列排序生成新序列,也可以使用list内置的sort()方法进行配序。下面看一下两种方法的区别:
- sorted()函数对所有可迭代的对象进行排序操作,排序后会返回一个新的对象。
- sort()函数属于List,是在原列表进行排序操作,不会返回新对象。
对于本题来说,除了要你排序以外,还给了你两个很关键的信息:
- 入参中给出了m、n两个参数,n就是nums2的实际长度,而m时nums1中有效长度。为什么说是有效呢?这是因为“假设nums1有足够的空间”,实际上是一个很长的全0列表,因此要写注意nums1种的有效元素
- “使nums1成为一个有序数组”,也就是说所有的操作都是在nums1这个对象中进行的,对象的地址不能变!
class Solution:
def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -> None:
nums1[:] = sorted(nums1[:m] + nums2)
为何要使用 nums1[:]? 是否可以将 nums1[:] 换为 nums1 ?
在信息2中,我们注意到,题目要求我们原地修改nums1对象,而不能新生成一个对象然后让nums1指向这一新对象。
注意在python3中,对象是一个盒子,有具体的地址,而变量名相当于是 "标签",可以贴在盒子上。我们需要辨析:nums1 = A 和 nums1[:] = A 的不同之处:
- nums1 = A :更改 nums1 这一变量名所指向的对象。让 nums1 变量指向 A 所指向的对象
- nums1[:] = A :对 nums1 指向的对象赋值。把 A 变量指向的对象的值逐个 copy 到 nums1 指向的对象中并覆盖 nums1 指向的对象的原来值。
nums1[:] 等价于 nums1[0:len(nums1)] 相当于取 nums1 对应的对象的一个视图,通常用这个来改变原对象的某几位值。
比如有时候,我们用 A[:2] = [0,1], 来改变 A 所指向的 list 对象的前两个值。而如果用 A = [0,1], 则是让 A 这一变量名指向新的 list 对象 [0,1]
下面的代码则验证了上面的解释:
# 对象在内存中的地址与id 一一对应,可以使用 id() 查看并判断是否是同一个对象
nums1 = [1,2,4,0,0]
print(id(nums1)) # 140125129895880
A = [1,2,3,4,5]
print(id(A)) # 140125129856640
nums1[:] = A
print(id(nums1))) # 140125129895880, 仍是原 list 对象, 只不过这一 list 对象的值发生了改变
# 若不执行 nums1[:] = A, 而执行
nums1 = A
print(id(nums1)) # 140125129856640, 不再是之前的那个 list 对象
示意图如下:
到这里我们就明白了为何要使用 nums1[:]。这里 sorted() 函数返回的必然是一个新的对象,因此我们需要 nums1[:], 而 [] 也代表一个新的 list 对象,我们需要用 nums1[:] = []。
解题思路(双指针从前往后)
一般来说,涉及到有序数组,可以想一想是不是可以使用双指针法来达到O(n+m)的时间复杂度。
最直接的算法实现是将指针p1置为nums1的开头, p2为nums2的开头,在每一步将最小值放入输出数组中。
由于 nums1 是用于输出的数组,需要将nums1中的前m个元素放在其他地方,也就需要 O(m)的空间复杂度。
class Solution(object):
def merge(self, nums1, m, nums2, n):
# 将nums1中的有效元素copy一份出来
nums1_copy = nums1[:m]
nums1[:] = []
# 设置好指针
p1, p2 = 0, 0
# 比较nums1_copy和nums2指针所指向元素的大小,较小的重新写入nums1
while p1 and p2 if nums1_copy[p1] nums1.append(nums1_copy[p1])
p1 += 1
else:
nums1.append(nums2[p2])
p2 += 1
# 剩下元素的处理
if p1 nums1[p1+p2:] = nums1_copy[p1:]
if p2 nums1[p1+p2:] = nums2[p2:]
解题思路(双指针从后往前)
对于双指针从前往后比较,需要使用额外空间。如果我们从nums1的末尾开始,则不需要额外空间。这里我们再使用一个指针p
来最终添加元素的位置。
class Solution(object):
def merge(self, nums1, m, nums2, n):
# 设置三个指针
p1, p2, p = m-1, n-1, m+n-1
while p1 >= 0 and p2 >= 0:
if nums1[p1] nums1[p] = nums2[p2]
p2 -= 1
else:
nums1[p] = nums1[p1]
p1 -= 1
p -= 1
# 如果nums1都比完了,nums2剩下的元素(nums比完了,nums1前面的不用变)
nums1[:p2 + 1] = nums2[:p2 + 1]