LeetCode-求两数组的中位数


继续一天一个算法题,额….这个要求真的有点高 o(╥﹏╥)o ,今天这个算法题在LeetCode上是一个困难级别的题,我在看完这个题后基本上有了个大概的思路,然而理想很丰满,现实很骨感,用代码实现算法却用了一天多。这里记录一下我的解题思路

寻找两个有序数组的中位数

给定两个大小为 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

备注:

  • 中位数:中位数的概念是初中的内容,简单来说,就是一个有序数组的中间那个数(如果数组长度是奇数,中位数是中间那个数;如果长度是偶数,中位数是中间两个数的平均数)
  • 有序数组:题干中有一点没讲清楚,就是只说了是两个有序数组,而没有说是两个升序数组,或者是两个降序数组,又或者一个升序一个降序。但我提交通过的代码,默认是处理两个数组是升序的,所以LeetCode这道题默认是针对两个升序数组的,我本地又测了一下两个降序和一个升序一个降序的情况,发现不能得到正确结果(后来我想了想,其实如果代码能正确得到两个升序数组的中位数,那么两个降序和一个升序一个降序的情况,只要稍加处理一下就行了,哈哈)

解题思路

所谓一千个人眼中有一千个哈姆雷特,不同的人对同一个题目有不同的算法思路,这里只记录我个人的解题思路。这道题要求算法复杂度是O(log(m + n)),这意味着只能遍历一次数组才能满足这个要求。而这道题最有用的价值就是两个数组已经是有序数组,要很好的利用这个特点。而不是首先想着把两个数组合成一个数组,然后再排好序,再求中位数。因此看完这道题时,我的脑海中有一个画面,想像有一个小人从数组的左边走到右边,他每次只能走一个数字,而且每走一步只能选择这两个数组中比较小的数走,然后走第二大的数,然后走第三大的数,依次这样走下去。当小人走的步数是两个数组长度的一半数时,他脚下的数字就是中位数了。这个思路的算法复杂度很小,没有任何多余的算法步骤,虽然思路是这样想的,但实际写代码时却总是不自觉得去遍历两个数组(因为发现如果不遍历,该怎么往右走呢),以至于花费了大半天时间还是得不到正确的结果,后来我再去仔细想了想这个思路过程,发现重要的不是遍历,而得需要知道下一步该怎么走(即是应该继续在这个数组上右移一步,还是应该跳到另一个数组)。也就是说我每走一步都需要规划下一步该怎么走,于是我写了一个死循环,然后每次循环时去规划下一次循环应走的代码逻辑(即 if...else...),然后再适当的时候跳出循环即可(即 if...break)。按这个思路这道题需要解决的问题点有三个:

  1. 动态规划:每右移一步时,要分别获取数组1和数组2的下一个元素,比较大小,然后选择较小的数组继续右移
  2. 临界判断:存在一种情况,就是数组1的值比数组2的值全都小时,数组1跑完后,到数组2时就不需要再获取数组1的元素做动态规划了
  3. 长度判断:当两数组长度和为奇数时比较好处理,当长度和为偶数时,跑到中间后,需把中间的数值先记下来,然后继续右移一位,这时再和刚刚记下的数值求和算中位数

代码实现

Java版本

public static double getMiddleByTwoNumbers(int[] nums1 ,int[] nums2){
        int value1=0,value2=0;
        // 声明两个游标,两个数组谁小,谁右移
        int i=0,j=0;
        int half = (nums1.length+nums2.length+1)>>1;
        boolean lengthFlag = ((nums1.length+nums2.length) & 1)==0;
        Integer temp1,temp0=null;
        boolean flag1=true,flag2=true;
        while (true){
            if(i<nums1.length)
                value1 = nums1[i];
            if(j<nums2.length)
                value2 = nums2[j];
            //(如果 num1当前值小于num2当前值,且游标未到达num1的最右边) 或者 (num2右标已到达最右边,此时只移动num1)
            if((value1<value2  && flag1) || !flag2){
                if(i >= nums1.length){
                    flag1 = false;
                    continue;
                }
                temp1 = value1;
                i++;
            }else {
                if(j >= nums2.length) {
                    flag2 = false;
                    continue;
                }
                temp1 = value2;
                j++;
            }
            // 如果已经到了中位数的位置
            if((i+j) == half){
                // 两数组长为偶数时,需找两个值
                if(lengthFlag){
                    if(temp0 == null){
                        half++;
                        temp0 = temp1;
                        continue;
                    }
                    return ((double) (temp1+temp0))/2d;
                }else {
                    return temp1;
                }
            }
        }
    }

Python版本

from typing import List


def getMiddleByTwoNumbers(nums1: List[int], nums2: List[int]) -> float:
     value1=0
     value2=0
     i=0
     j = 0
     half = (len(nums1) + len(nums2) + 1) // 2
     lengthFlag = (len(nums1) + len(nums2))%2 == 0
     tempFlag = False
     temp1=0
     temp0=0
     flag1 = True
     flag2 = True
     while True:
         if i<len(nums1):
             value1=nums1[i]
         if j<len(nums2):
             value2=nums2[j]

         if (value1<value2 and flag1) or not flag2:
             if i>= len(nums1):
                 flag1 = False
                 continue
             temp1 = value1
             i = i+1
         else:
             if j>= len(nums2):
                 flag2 = False
                 continue
             temp1 = value2
             j = j+1
         if (i+j) == half:
             if lengthFlag:
                 if not tempFlag:
                     tempFlag = True
                     half = half+1
                     temp0 = temp1
                     continue
                 return (temp0+temp1)/2
             else:
                 return temp1

Go版本

func getMiddleByTwoNumbers( num1 []int ,num2 []int) float64 {
    var value1 int = 0
    var value2 int = 0
    // 声明两个游标
    var i int = 0
    var j int = 0
    var half int = (len(num1)+len(num2)+1)/2
    var lengthFlag bool = (len(num1)+len(num2))%2==0
    // 两数组长度为偶数时,额外处理的开关
    var even bool = false
    var temp0 int
    var temp1 int
    var flag1 bool =true
    var flag2 bool =true
    for true{
        if i<len(num1){
            value1 = num1[i]
        }
        if j<len(num2){
            value2 = num2[j]
        }

        if (value1<value2 && flag1) || !flag2 {
            if i>= len(num1){
                flag1=false
                continue
            }
            temp1 = value1
            i++
        }else {
            if j>= len(num2){
                flag2=false
                continue
            }
            temp1 = value2
            j++
        }

        if (i+j) == half{
            // 如果两数组长度和为偶数
            if lengthFlag {
                // 如果是false,说明中位数还需要拿一位
                if !even {
                    half++
                    temp0 = temp1
                    even = true
                    continue
                }
                return float64(temp0+temp1)/2.0
            }else {
                return float64(temp1)
            }
        }
    }
    return float64(temp1)
}

LeetCode运行结果

Java的优化确实已经登峰造极了,三种语言代码是一样的思路写的,Java还是跑的最快的,只不过内存用的有点多,毕竟Java有个JVM在那


Author: 顺坚
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint polocy. If reproduced, please indicate source 顺坚 !
评论
 Previous
React-Native项目搭建 React-Native项目搭建
React Native 结合了 Web 应用和 Native 应用的优势,可以使用 JavaScript 来开发 iOS 和 Android 原生应用。在 JavaScript 中用 React 抽象操作系统原生的 UI 组件,代替 DO
2020-05-05
Next 
LeetCode-两数相加 LeetCode-两数相加
在代码的世界中有一句名言,即程序=数据结构+算法。工作中大部分开发其实都用不到多高深的算法,因为前辈们已经造好了各种高级轮子,我们要做的仅仅只需要调用他们封装好的API就行了。这样就算不懂这些知识,只要Java API、开发框架用得熟练,照
2020-04-19
  TOC