【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): 1 3 5 7 9  
nums2(len2 = 6): 0 2 4 6 8 10

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

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

nums1 1 3 5 7 9  
nums2 0 2 4 6 8 10

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

nums1 1 3 5 7 9
nums2 6 8 10    

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

nums1 1 3 5 7 9
nums2 6 8 10    

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

nums1 3 5 7 9
nums2 6 8 10  

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

nums1 5 7 9
nums2 6 8 10

此时需要分情况考虑:

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

方法3:变种二分法

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

假设两个原始数组如下:

nums1 1 3 5 7 9  
nums2 0 2 4 6 8 10

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

nums1 1 3 5   7 9  
nums2 0 2 4   6 8 10

这样子就将两个数组都分割为两部分,我们把两个数组的左半边和右半边分别合并为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
        
发布了100 篇原创文章 · 获赞 4 · 访问量 1480

猜你喜欢

转载自blog.csdn.net/songyuwen0808/article/details/105276817