zoukankan      html  css  js  c++  java
  • 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
    
  • 相关阅读:
    UVA 10604 Chemical Reaction
    UVA 10635 Prince and Princess
    UVA 607 Scheduling Lectures
    Create Maximo Report
    安裝及配置Maximo Report步驟
    check blocking
    數據據類型縮寫
    .net
    poj3522
    poj1286
  • 原文地址:https://www.cnblogs.com/chenjin2018/p/14064144.html
Copyright © 2011-2022 走看看