原创 lightcity 光城 2018-09-28

LeetCode攀登之旅(4)_c++

LeetCode攀登之旅(4)

0.说在前面

1.两个排序数组中位数

2.二分查找法

3.作者的话

0.说在前面


本节主要研究如何用二分查找算法去实现两个排序数组中位数,以及如何用python去实现。


1.两个排序数组中位数


原题如下

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

对于这道题而言,采用下面这个方法是最容易想到的,直接将两个有序的数组或者说列表进行合并,而在python中合并直接是相加就可以了。拼接后,只需要排序,便可以得到一个有序的list,那么对这个list进行操作取其中的中位数即可。这个方法虽然快捷,也可以在leetcode上通过,但是其时间复杂度不满足题意。

下面这段代码的空间复杂度为O(m+n),时间复杂度为O(1)(这里的m,n分别为nums1与mums2的长度,对于时间复杂度,这里的len(t)的时间复杂度为O(1),而对于其他的操作只是单运算,没有涉及循环,故最后的时间复杂度为O(1)。而空间复杂度则按照t的长度(m+n))计算。

因为O(1)<O(log(m+n)),故可以通过。

class Solution:
    def findMedianSortedArrays(self, nums1, nums2):
        """
        :type nums1: List[int]
        :type nums2: List[int]
        :rtype: float
        """

        t = nums1+nums2 # 两个拼接
        t.sort(); # 合并后排序
        l = len(t)
        medium = l//2 # 只取整数部分
        if l%2==1:
            return float(t[medium]) # 奇数直接返回中间
        else:
            return (t[medium-1]+t[medium])/2.0 # 偶数返回前后相加除以2

2.二分查找法


二分查找法,称折半查找或者对数搜索。

在考研时,考数据结构,记得用此方法的时间复杂度为log(n)。

假设总共有n个元素,每次查找的区间大小就是n,n/2,n/4,…,n/2^k,其中k就是循环的次数。  由于n/2^k取整后>=1,即令n/2^k=1,  可得k=log2n,(是以2为底,n的对数)

对于上述这道题而言,采用这个二分查找法,可以很精确的得到相对应的时间复杂度,满足题意。

下面来阐述二分查找法在本题中的思想。

对于求中位数,分为两种情况,分别为偶数与奇数情况。

(1)当两个数组的长度之和为偶数时,最终得到的中位数为两个中间数之和除以2;

(2)而当两个数组的长度之和为奇数时,最终得到的中位数刚好为中间数。

题中所知,两个数组为有序数组,那么我们能否按照下手稿所示,通过切分,将nums1切分为左右两部分,nums2同此操作,最后有四个数,分别是x2,x3,y5,y6为我们最终中位数的考量范围。

如果此时恰好x2<y6,并且y5<x3那么我们此时得到了最终的左右区间。

此时如果两个数组之和为奇数,那么直接获取在x2与y5中取最大值,即为最终结果。

如果为偶数,则需要计算左边x2与y5的最大值,x3与y6的最小值,最大值与最小值之和除以2即为我们的结果。

如果当x2与y6 或者 y5与x3两组比较过程中发现,至少有一组不满足以上条件,那么继续寻找划分,至于具体怎么划分,看后面的例子(参考以下手稿)即可。

二分查找模型图

例子推导图1

LeetCode攀登之旅(4)_c++_02

例子推导图2

python实现

实现逻辑如下:

(1)首先比较两个数组的长度,将nums1存储长度较小的,nums2存储长度较大的,便于后面的划分;

(2)定义循环的区间low与hight;

(3)首次的X左区间取上述循环区间范围(这里看手稿);

(4)partitionY-1就是上述的y5,partitionX为上述的x3,此时就得让x的左区间扩大,那么也就是让low往上升,便可以达到增大的效果,然后逐步逼近;

(5)partitionX-1就是上述的x2,partitionY就是上述的y6,当x2>y6时,此时左边数字太大,得右移,即让X的左区间缩小,也就是让high减小,然后逐步逼近;

(6)前面讨论时没有讨论到边界问题的,所以其他的情况则是讨论边界特殊化;

(7)当nums1左区间直接为0,也就是如nums1=[5,6],nums2=[3,4]这种情况,那么此时左边最大是参考的左区间y或者nums2,同理nums2左区间直接为0,则参考左区间x或者nums1;

(8)此时对于左区间直接可求出来,那么只要两个数组的总和为奇数,直接取下面最大

nums1[partitionX-1],nums2[partitionY-1]

或者上述的(7)的两种情况。

(9)最后就是右区间处理,同左区间,求出右区间最小,然后与上述的左区间最大值求和除以2即为偶数情况下的中位数。

class Solution:
    def findMedianSortedArrays(self, nums1, nums2):
        if len(nums1) > len(nums2):
            return self.findMedianSortedArrays(nums2,nums1)
        x = len(nums1)
        y = len(nums2)
        low = 0
        high = x
        while low <= high:
            partitionX = int((low+high)/2)
            partitionY = int((x+y+1)/2 - partitionX)
            if partitionX<x and nums2[partitionY-1]>nums1[partitionX]:
                low = partitionX + 1
            elif partitionX>0 and nums1[partitionX-1]>nums2[partitionY]:
                high = partitionX - 1
            else:
                if partitionX==0:
                    maxLeft =  nums2[partitionY-1]
                elif partitionY==0:
                    maxLeft = nums1[partitionX-1]
                else:
                    maxLeft = max(nums1[partitionX-1],nums2[partitionY-1])
                if (x+y)%2==1:
                    return maxLeft
                if partitionX == x:
                    minRight = nums2[partitionY]
                elif partitionY == y:
                    minRight = nums1[partitionX]
                else:
                    minRight = min(nums1[partitionX],nums2[partitionY])

                return (maxLeft+minRight)/2.0
nums1 = [1,10,12]
nums2 = [3]
s = Solution()
res = s.findMedianSortedArrays(nums1,nums2)
print(res)

最后看一下提交的耗及击败人数,95%左右,这个是最好的结果,有时候是60%左右,所以不能完全看这个。

LeetCode攀登之旅(4)_c++_03

提交结果图

3.作者的话


最后,您如果觉得本公众号对您有帮助,欢迎您多多支持,转发,谢谢!

更多刷题,请关注本公众号算法系列!