【leetcode系列】【算法】【困难】寻找两个有序数组的中位数

题目:

题目链接: https://leetcode-cn.com/problems/median-of-two-sorted-arrays/

中位数概念: https://baike.baidu.com/item/%E4%B8%AD%E4%BD%8D%E6%95%B0

 

解题思路:

方法一: 归并排序

时间复杂度:O(M + N)

利用归并排序的思想,拼接并排序两个数组,并利用合并后的有序数组,获取中位数

 

方法二:转换为寻找第k小的数字

时间复杂度:O(log(M + N))

假设两个原始数组如下:

nums1(len1 = 5):13579 
nums2(len2 = 6):0246810

总长度total_len = len1 + len2 = 11,中位数应该为其中的第index = (11 + 1) / 2 = 6个数字

在对数组进行遍历时,同时获取两个数组的前 half_idx = index / 2 = 3个数组进行比对(如果index为奇数,则向前取整),如下图:

nums113579 
nums20246810

 

然后比对两个数组中截取序列中最后一个数字的大小,比对时发现4 < 5,则nums2的前3个数字,一定都不是按顺序的第6个数字,此时将nums2的前3个元素删除(不需要真的删除,可以添加一个start_idx记录),更新后的数组如下:

nums113579
nums26810  

更新index = index - 3 = 3,再次进行上述步骤,此时half_idx = index / 2 = 3 / 2 = 1,如下图:

nums113579
nums26810  

此时1 < 6,所以删除nums1的前1个元素,更新index = index - 1 = 3 - 1 = 2,half_idx = index / 2 = 2 / 2 = 1,继续比对:

nums13579
nums26810 

3 < 6,继续删除nums1的前1个元素,更新index = index - 1 = 2 - 1 = 1,half_idx = index / 2 = 1 / 2 = 0,停止截取,此时两个数组结果如下:

nums1579
nums26810

此时需要分情况考虑:

  1. 如果原始total_len为奇数,返回min(nums1[0], nums2[0])
  2. 如果原始total_len为偶数,返回(nums1[0] + nums2[0]) / 2

 

方法3:变种二分法

时间复杂度:O(log(min(M, N)))

假设两个原始数组如下:

nums113579 
nums20246810

对于每个数组,都可以使用一个分割线,分割为左右两部分,如下图(中间空的列,当做分割线):

nums1135 79 
nums2024 6810

这样子就将两个数组都分割为两部分,我们把两个数组的左半边和右半边分别合并为left_part、right_part

分别设变量含义如下:

i : nums1左半部分的长度,也是右半部分第一个数字的下标,对于上述数组,即数字7在nums1中的下标

j : nums2左半部分的长度,也是右半部分第一个数字的下标,对于上述数组,即数字6在nums2中的下标

m : nums1的长度

n : nums2的长度

m - i : nums1右半部分的长度

n - j : nums2右半部分的长度

left_part :两个数组左半部分的总集

right_part :两个数组右半部分的总集

这样,我们只要满足下面几个条件,就可以直接通过i、j获得中位数:

  1. left_part中数字总数,与right_part数字总数相等,或者相差1
  2. left_part中的最大数字,小于right_part中的最小数字

那么如何更新i和j,直到符合条件呢,循环时候的流程如下:

  1. 初始化i的最小值i_min = 0, 最大值i_max = m,长度一半为half_len = (i_min + i_max + 1) // 2
  2. 更新当前的i = (i_min + i_max) // 2, j = half_len - i
  3. 如果i > 0 && j < n && nums1[i - 1] > nums2[j],说明当前nums1左半部分的最大值,比nums2右半部分的最小值要大,不符合要求,需要减小nums1左半部分的最大值,即将i向左移动,同时会导致j在nums2中向右移动,更新i_max = i - 1,并继续循环
  4. 如果j > 0 && i < m && nums2[j - 1] > nums1[i],说明当前nums2左半部分的最大值,比nums1右半部分的最小值要大,不符合要求,需要增大nums1左半部分的最大值,即将i向右移动,同时会导致j在nums2中向左移动,更新i_min = i + 1,并继续循环
  5. 其他情况,说明找到了中位数,此时按照下记逻辑进行左半部分最大值left_max和右半部分最小值right_min的计算
    1. left_max计算逻辑
      1. 如果i == 0,说明nums1所有数字都属于right_part,所以left_max = nums2[j - 1]
      2. 如果j == 0,说明nums2所有数字都属于right_part,所以left_max = nums1[i - 1]
      3. 其余情况,说明nums1和nums2都有部分属于left_part,则left_max = max(nums1[i - 1], nums2[j - 1])
    2. right_max计算逻辑
      1. 如果i == m,说明nums1所有数字都属于left_part,所以right_min = nums2[j]
      2. 如果j == n, 说明nums2所有数字都属于left_part,所以right_min = nums1[i]
      3. 其他情况,说明nums1和nums2都有部分属于right_part,所以right_min = min(nums1[i], nums2[j])
  6. 计算完成left_max和right_min之后,如果总数字个数为奇数个,直接返回left_max即可,如果为偶数个,则返回(left_max + right_min) / 2

 

代码实现(只实现了方法三):

class Solution:
    def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
        len1, len2 = len(nums1), len(nums2)
        if len1 > len2:
            nums1, nums2 = nums2, nums1
            len1, len2 = len2, len1
        
        if len2 == 0:
            return 0
        
        imin, imax, half_len = 0, len1, (len1 + len2 + 1) // 2
        while imin <= imax:
            i = (imin + imax) // 2
            j = half_len - i
            if i > 0 and j < len2 and nums1[i - 1] > nums2[j]:
                imax = i - 1
            elif j > 0 and i < len1 and nums2[j - 1] > nums1[i]:
                imin = i + 1
            else:
                left_max, right_min = 0, 0
                if i == 0:
                    left_max = nums2[j - 1]
                elif j == 0:
                    left_max = nums1[i - 1]
                else:
                    left_max = max(nums1[i - 1], nums2[j - 1])
                    
                if (len1 + len2) % 2 == 1:
                    return left_max
                
                if i == len1:
                    right_min = nums2[j]
                elif j == len2:
                    right_min = nums1[i]
                else:
                    right_min = min(nums1[i], nums2[j])
                    
                return (left_max + right_min) / 2
        

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值