LeetCode刷题实战4:寻找两个正序数组的中位数

题目描述

      给定两个大小为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的中位数。

示例 1

输入:nums1 = [1,3], nums2 = [2]
输出:2.00000
解释:合并数组 = [1,2,3] ,中位数 2

示例 2

输入:nums1 = [1,2], nums2 = [3,4]
输出:2.50000
解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5

示例 3

输入:nums1 = [0,0], nums2 = [0,0]
输出:0.00000

示例 4

输入:nums1 = [], nums2 = [1]
输出:1.00000

示例 5

输入:nums1 = [2], nums2 = []
输出:2.00000

解答

方法 1

      给定两个正序的数组,要求返回两个正序数组的中位数,最容易想到的方法就是先将两个数组进行合并,然后再返回合并后的大数组的中位数即可。我们的第一种方法,也是用到数组合并的思想,但并不是完全合并两个数组。

      两个数组的长度分别为 m 和 n,总长度 total_len = m + n,当 total_len 为奇数时,中位数的下标 mid 为 total_len // 2;为偶数时,mid = total_len / 2,此时中位数为 mid-1 和 mid 的下标对应值之和再除以 2。

      先定义一个空的列表 new_arry。i,j 分别对应两个数组对应的下标,并比较 nums1[i] 和 nums2[j] 的大小,将值小的插入到 new_arry 中。

      当 new_arry 的长度等于 mid 时,循环停止。

      如果 total_len 为奇数,则返回 new_arry[mid] 即可;如果为偶数,则返回 (new_arry[mid-1] + new_arry[mid]) / 2.0。

class Solution:
    def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
        res = 0.0
        new_array = []
        m, n = len(nums1), len(nums2)
        total_len = m + n
        if total_len % 2 != 0:
            mid = int(total_len//2)
        else:
            mid = int(total_len/2)
        i,j = 0,0
        while len(new_array) -1 < mid:
            if i < m and j < n :
                if nums1[i] < nums2[j]:
                    new_array.append(nums1[i])
                    i+=1
                    continue
                if nums1[i] >= nums2[j]:
                    new_array.append(nums2[j])
                    j+=1
                    continue
            if i == m and j < n:
                new_array.append(nums2[j])
                j+=1
            if i < m and j == n:
                new_array.append(nums1[i])
                i+=1
        if total_len %2 != 0:
            res = new_array[mid]
        else:
            res = (new_array[mid-1] + new_array[mid])/2.0
        return res

方法 2

      中位数是一个数组中间位置的数,其实中位数把一个升序数组分成了长度相等的两部分,其中左半部分的最大值永远小于或等于右半部分的最小值。我们用一个例子来说明,比如现在有如下数组:

[1,2,3,5,67,7,8,9,15]

[1,2,3,5,6] < [7,7,8,9,15]

      如上所示,对于偶数长度的数组,可以根据中位数分成长度相等的两部分,左半部分的最大元素(6),永远小于或等于右半部分的最小元素(7)。

      对于奇数长度的数组,同样可以根据中位数分成两部分:

[1,2,3,4,5,5,6,7,9,10,11]

[1,2,3,4,5,5] < [6,7,9,10,11]

      如上所示,对于奇数长度的数组,如果把中位数本身归入左半部分,则左半部分长度 = 右半部分长度 + 1。

      左半部分的最大元素(5),永远小于或等于右半部分的最小元素(6)。

      大数组被中位数等分的左右两部分,每一部分根据来源又可以再划分成两部分,其中一部分来自数组 nums1 的元素,另一部分来自数组 nums2 的元素:

数组 nums1        数组 nums2
[3,5,7,8,9] < [1,2,6,7,15]
      i                   j

[1,2,3,5,6,7,7,8,9,15]
大数组         i+j

      如上所示,原始数组 nums1 和数组 nums2,各自分成黑色和橙色两部分。其中数值较小的黑色元素组成了大数组的左半部分,数组较大的橙色元素组成了大数组的右半部分。

      最重要的是,黑色元素和橙色元素的数量是相等的(偶数情况),而且最大的黑色元素小于或等于最小的橙色元素。

      假设数组 nums1 的长度是 m,黑色和橙色元素的分界点是 i,数组 nums2 的长度是 n,黑色和橙色元素的分界点是 j,那么为了让大数组的左右两部分相等,则 i 和 j 需要符合如下条件: i + j = (m + n + 1) / 2(之所以 m + n 后面要再加 1,是为了应对大数组长度为奇数的情况),max(nums1[i-1], nums2[j-1])<= min(nums1[i], nums2[j])(直白地说,就是最大的黑色元素小于或等于最小的橙色元素)。

      由于 m + n 的值是恒定的,所以我们只要确定一个合适的 i,就可以确定 j,从而找到大数组左半部分和右半部分的分界,也就找到了归并之后大数组的中位数。

      对于这个合适的 i 值,我们采用二分查找的思路来寻找。下面通过一个具体的例子来看看二分查找确定 i 值:

数组 nums1:[3,5, 6, 7, 8, 15,20]
数组 nums2:[1,10,12,18,21,24,25,27]

      第一步,就像二分查找那样,把 i 设在数组 nums1 的正中位置,也就是让 i = 3;

数组 nums1:[3,5, 6, 7, 8, 15,20]
i 数组 nums2:[1,10,12,18,21,24,25,27]

      第二步,根据 i 的值来确定 j 的值,j = (m + n + 1) / 2 - i = 5;

数组 nums1:[3,5, 6, 7, 8, 15,20]
                  i
数组 nums2:[1,10,12,18,21,24,25,27]
j

      第三步,验证 i 和 j,分为下面三种情况:

      1. nums2[j-1] <= nums1[i] && nums1[i-1] <= nums2[j]

            说明 i 和 j 左侧的元素都小于或等于右侧的元素,这一组 i 和 j 是我们想要的。

      2. nums1[i] < nums2[j-1]

            说明 i 对应的元素偏小了,i 应该向右侧移动。

      3. nums1[i-1] > nums2[j]

            说明 i-1 对应的元素偏大了,i 应该向左侧移动。

      显然,上面的例子中属于情况 2,nums1[3] < nums2[5],所以 i 应该向右移动。

      第四步,在数组 nums1 的右半部分,重新确定 i 的位置,就像二分查找一样:

数组 nums1:[3,5, 6, 7, 8, 15,20]
                          i
数组 nums2:[1,10,12,18,21,24,25,27]

      第五步,同第二步,根据 i 来确定 j 的值,j = (m + n + 1) / 2 - i = 3:

数组 nums1:[3,5, 6, 7, 8, 15,20]
                          i
数组 nums2:[1,10,12,18,21,24,25,27]
                  j

      第六步,同第三步,验证 i 和 j:

            由于nums1[5] >= nums2[2] 且 nums2[3] >= nums1[4],所以这一组 i 和 j 是合适的。

      第七步,找出中位数:

            如果大数组的长度是奇数,那么:中位数 = max(nums1[i-1], nums2[j-1])(也就是大数组左半部分的最大值)。

            如果大数组的长度是偶数,那么:中位数 = (max(nums1[i-1], nums2[j-1]) + min(nums1[i], nums2[j])) / 2(也就是大数组左半部分的最大值和大数组右半部分的最小值取平均)。

对于一些特殊情况

1. 数组 nums1 的长度大于数组 nums2

      提前把数组 nums1 和数组 nums2 进行交换,较短的数组放在前面,i 从较短的数组中取。这样做还有一个好处,由于数组 nums1 是较短数组,i 的搜索次数减少了。

2. 无法找到合适的 i 值

      什么情况下会无法找到合适的 i 值呢?有两种情况:

      数组 nums1 的长度小于数组 nums2 的长度,并且数组 nums1 的所有元素都大于数组 nums2 的元素。

      在这种情况下,无法通过二分查找寻找符合 nums2[j-1] <= nums1[i] && nums1[i-1] <= nums2[j] 的 i 值,一直到 i=0 为止。此时我们可以跳出二分查找的循环,所求的中位数是 nums2[j-1]。(仅限奇数情况)

      数组 nums1 的长度小于数组 nums2 的长度,并且数组 nums1 的所有元素都小于数组 nums2 的元素

      在这种情况下,同样无法通过二分查找寻找符合 nums2[j-1] <= nums1[i] && nums1[i-1] <= nums2[j] 的 i 值,一直到 i=数组 nums1 的长度-1 为止。此时我们可以跳出二分查找的循环,所求的中位数是 max(nums1[i-1], nums2[j-1])。(仅限奇数情况)

class Solution:
    def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
        m, n = len(nums1), len(nums2)
        if m>n:
            nums1, nums2, m, n=nums2, nums1, n, m
        start, end, half_len = 0, m, (m + n + 1)//2
        while start<=end:
            i = (start+end)//2
            j = half_len - i
            if i < m and nums2[j-1] > nums1[i]:
                start = i + 1
            elif i > 0 and nums1[i-1] > nums2[j]:
                end = i - 1
            else:
                if i == 0:
                    max_of_left = nums2[j-1]
                elif j == 0:
                    max_of_left = nums1[i-1]
                else:
                    max_of_left = max(nums1[i-1], nums2[j-1])
                if (m + n)%2 == 1:
                    return max_of_left
                if i == m:
                    min_of_right = nums2[j]
                elif j == n:
                    min_of_right = nums1[i]
                else:
                    min_of_right = min(nums1[i], nums2[j])
                return (max_of_left + min_of_right)/2.0
原文地址:https://www.cnblogs.com/chenjin2018/p/14064144.html