力扣4:寻找两个有序数组的中位数

来源:力扣

  • 给定两个大小为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。

  • 请你找出这两个正序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n))。

  • 你可以假设 nums1 和 nums2 不会同时为空。

示例 1:

nums1 = [1, 3]
nums2 = [2]

则中位数是 2.0

示例 2:

nums1 = [1, 2]
nums2 = [3, 4]

则中位数是 (2 + 3)/2 = 2.5
分析
1、暴力解法:调包加排序
  • 合并数组为nums = nums1 + nums2;
  • 对其进行排序sorted(nums);
  • 直接判断len(nums)的奇偶性,取中间的索引对应的值。
class Solution:
    def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
        nums = nums1 + nums2
        # nums.sort()
        nums = sorted(nums)
        n = len(nums)
        if n % 2 == 1:
            return nums[n // 2]
        else:
            return (nums[n//2] + nums[n//2 - 1])/2
2、双指针:先排序合并再取中间值
  • 这里首先参考力扣88
    给你两个有序整数数组 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]

# 两种解法:双指针从前都后移动和从后往前移动
#该题要求合并到nums1中,而不是新建一个,所以使用sorted函数时候要注意nums1[:]=bulabula,这样就不会修改nums1
class Solution(object):
    def merge(self, nums1, m, nums2, n):
        """
        :type nums1: List[int]
        :type m: int
        :type nums2: List[int]
        :type n: int
        :rtype: void Do not return anything, modify nums1 in-place instead.
        """
        # Make a copy of nums1.
        nums1_copy = nums1[:m] 
        nums1[:] = []

        # Two get pointers for nums1_copy and nums2.
        p1 = 0 
        p2 = 0
        
        # Compare elements from nums1_copy and nums2
        # and add the smallest one into nums1.
        while p1 < m and p2 < n: 
            if nums1_copy[p1] < nums2[p2]: 
                nums1.append(nums1_copy[p1])
                p1 += 1
            else:
                nums1.append(nums2[p2])
                p2 += 1

        # if there are still elements to add
        if p1 < m: 
            nums1[p1 + p2:] = nums1_copy[p1:]
        if p2 < n:
            nums1[p1 + p2:] = nums2[p2:]
#时间复杂度O(n+m),空间复杂度O(m)

解法2:

class Solution:
    def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -> None:
        """
        Do not return anything, modify nums1 in-place instead.
        """
        # nums1[:] = sorted(nums1[:m] + nums2)
        p1 = m - 1
        p2 = n - 1
        p = m+n-1
        while p1>=0 and p2>=0:
            if nums1[p1] < nums2[p2]:
                nums1[p] = nums2[p2]
                p2 -= 1
            else:
                nums1[p] = nums1[p1]
                p1 -= 1
            p-=1
        # 如果nums1为空,则单独处理
        nums1[:p2+1] = nums2[:p2+1]
        
针对这个题,我们利用倒序双指针
class Solution:
    def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
		m, n = len(nums1), len(nums2)
        p1 = m-1
        p2 = n-1
        p = m + n - 1
        # 单独定义一个列表空间,用来存放新的有序数组
        nums = [0]*(m+n)
        # 终止条件就是有一个数组遍历完
        while p1>=0 and p2>=0:
            if nums1[p1] >= nums2[p2]:
                nums[p] = nums1[p1]
                p1 -= 1
            else:
                nums[p] = nums2[p2]
                p2 -= 1
            p -= 1
        # 额外判断某一个数组遍历完,需要把另外一个数组直接拼接进去
        if p1<0:
            nums[:p2+1] = nums2[:p2+1]
        if p2<0:
            nums[:p1+1] = nums1[:p1+1]
        # 判断数组长度的奇偶性
        if len(nums)%2 == 1:
            return nums[(m+n)//2]
        else:
            return (nums[(m+n)//2]+nums[(m+n)//2-1])/2

3、二分查找(第K小数)

根据中位数的定义,当 m+nm+nm+n 是奇数时,中位数是两个有序数组中的第 (m+n)/2(m+n)/2(m+n)/2 个元素,当 m+nm+nm+n 是偶数时,中位数是两个有序数组中的第 (m+n)/2(m+n)/2(m+n)/2 个元素和第 (m+n)/2+1(m+n)/2+1(m+n)/2+1 个元素的平均值。因此,这道题可以转化成寻找两个有序数组中的第 kkk 小的数,其中 kkk 为 (m+n)/2(m+n)/2(m+n)/2 或 (m+n)/2+1(m+n)/2+1(m+n)/2+1。

假设两个有序数组分别是 A 和 B。要找到第 k 个元素,我们可以比较 A[k/2−1] 和 B[k/2−1],其中 / 表示整数除法。由于 A[k/2−1] 和 B[k/2−1]的前面分别有 A[0 : k/2−2] 和 B[0 :k/2−2],即 k/2−1个元素,对于 A[k/2−1]和 B[k/2−1]中的较小值,最多只会有 (k/2−1)+(k/2−1)≤k−2 个元素比它小,那么它就不能是第 k 小的数了。

因此我们可以归纳出三种情况:

  • 如果 A[k/2−1]<B[k/2−1],则比 A[k/2−1] 小的数最多只有 A的前 k/2−1个数和 B的前 k/2−1个数,即比 A[k/2−1] 小的数最多只有 k−2个,因此 A[k/2−1]不可能是第 k 个数,A[0]到 A[k/2−1]也都不可能是第 k 个数,可以全部排除。

  • 如果 A[k/2−1]>B[k/2−1],则可以排除 B[0] 到 B[k/2−1]。

  • 如果 A[k/2−1]=B[k/2−1],则可以归入第一种情况处理。

可以看到,比较 A[k/2−1]和 B[k/2−1]之后,可以排除 k/2 个不可能是第 k 小的数,查找范围缩小了一半。同时,我们将在排除后的新数组上继续进行二分查找,并且根据我们排除数的个数,减少 k 的值,这是因为我们排除的数都不大于第 k 小的数。

有以下三种情况需要特殊处理:

  • 如果 A[k/2−1] 或者 B[k/2−1] 越界,那么我们可以选取对应数组中的最后一个元素。在这种情况下,我们必须根据排除数的个数减少 k 的值,而不能直接将 k 减去 k/2。

  • 如果一个数组为空,说明该数组中的所有元素都被排除,我们可以直接返回另一个数组中第 k 小的元素。

  • 如果 k=1,我们只要返回两个数组首元素的最小值即可。

用一个例子说明上述算法。假设两个有序数组如下:

A: 1 3 4 9
B: 1 2 3 4 5 6 7 8 9

两个有序数组的长度分别是 4 和 9,长度之和是 13,中位数是两个有序数组中的第 7个元素,因此需要找到第 k=7 个元素。

比较两个有序数组中下标为 k/2−1=2 的数,即 A[2]和 B[2],如下面所示:

A: 1 3 4 9
-------- ↑
B: 1 2 3 4 5 6 7 8 9
-------- ↑

由于 A[2]>B[2],因此排除 B[0]到 B[2],即数组 B 的下标偏移(offset)变为 3,同时更新 k的值:k=k−k/2=4。(其实为K减去偏移量来更新K,K -= offset

下一步寻找,比较两个有序数组中下标为 k/2−1=1 的数,即 A[1]和 B[4],如下面所示,其中方括号部分表示已经被排除的数。

A: 1 3 4 9
------ ↑
B: [1 2 3] 4 5 6 7 8 9
--------------- ↑

由于 A[1]<B[4],因此排除 A[0]到 A[1],即数组 A的下标偏移变为 2,同时更新 k 的值:k=k−k/2=2。

下一步寻找,比较两个有序数组中下标为 k/2−1=0的数,即比较 A[2]和 B[3],如下面所示,其中方括号部分表示已经被排除的数。

A: [1 3] 4 9
---------- ↑
B: [1 2 3] 4 5 6 7 8 9
------------ ↑

由于 A[2]=B[3],根据之前的规则,排除 A中的元素,因此排除 A[2],即数组 A的下标偏移变为 3,同时更新 k 的值: k=k−k/2=1。

由于 k 的值变成 1,因此比较两个有序数组中的未排除下标范围内的第一个数,其中较小的数即为第 k 个数,由于 A[3]>B[3],因此第 k 个数是 B[3]=4。

A: [1 3 4] 9

B: [1 2 3] 4 5 6 7 8 9

class Solution:
    def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
        # 第K小数
        # def getK(k):
        #     index1, index2 = 0, 0
        #     while True:
                    #终止条件
                    # 一个为空,直接返回另一个第K个元素,这时的K已经更新过了
        #         if index1 == m:
        #             return nums2[index2 + k - 1]
        #         if index2 == n:
        #             return nums1[index1 + k - 1]
                  #K为1说明已经查找到K,只需要比较这时小的元素即可
        #         if k == 1:
        #             return min(nums1[index1], nums2[index2])
        #         new1 = min(index1 + k//2 -1, m-1)
        #         new2 = min(index2 + k//2 -1, n-1)
        #         p1, p2 = nums1[new1], nums2[new2]
        #         if p1 <= p2:
        #             k -= new1 - index1 + 1
        #             index1 = new1 + 1
        #         else:
        #             k -= new2 -index2 + 1
        #             index2 = new2 + 1
        # m, n = len(nums1), len(nums2)
        # num = m + n
        # if num % 2 == 1:
        #     return getK((num + 1) // 2)
        # else:
        #     return (getK(num//2)+getK((num//2 + 1)))/2

        # 暴力解法,直接合并
        # nums = nums1 + nums2
        # # nums.sort()
        # nums = sorted(nums)
        # n = len(nums)
        # if n % 2 == 1:
        #     return nums[n // 2]
        # else:
        #     return (nums[n//2] + nums[n//2 - 1])/2


        # 双指针
        # 左右指针初始值,中位数位置
        m, n = len(nums1), len(nums2)
        p1 = m-1
        p2 = n-1
        p = m + n - 1
        nums = [0]*(m+n)
        while p1>=0 and p2>=0:
            if nums1[p1] >= nums2[p2]:
                nums[p] = nums1[p1]
                p1 -= 1
            else:
                nums[p] = nums2[p2]
                p2 -= 1
            p -= 1
        if p1<0:
            nums[:p2+1] = nums2[:p2+1]
        if p2<0:
            nums[:p1+1] = nums1[:p1+1]
        if len(nums)%2 == 1:
            return nums[(m+n)//2]
        else:
            return (nums[(m+n)//2]+nums[(m+n)//2-1])/2


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值