[LeetCode]#4 Median of Two Sorted Arrays

一、题目

There are two sorted arrays nums1 and nums2 of size m and n respectively. Find the median of the two sorted arrays. The overall run time complexity should be O(log (m+n)).

 

二、解析

该题是让求两个有序序列的中位数,时间复杂度为O(log(m+n))。其实拿到这个题第一想法是把两个序列合并成一个,然后直接输出中间的元素即可,这样的话两个list每个元素都要过一遍,所以复杂度为O(m+n)。由于LeetCode本题数据太弱,所以随便一试就过了,额。。见代码一。

但是注意到时间复杂度,和“有序”这个条件,则说明可以用折半的方法去做。一时没了想法,如何用折半?去看Discuss,基本都是将求中位数的问题转化成求第K小的问题。什么意思呢?比如a=[1,2,3,4,5],中位数就是第三个元素3,也就是第3小的数。找到这个数即可。既然复杂度要求这么高,那么一定是不能进行合并、从头遍历这样的操作的,所以问题转化成为:在不合并两个序列的情况下,找出合并后应有的中位数。参照他人思路,有以下共识:

首先假设a=[7,8,9], b = [1,2,3,5],中位数为5,转化为找第(3+4)/2 + 1 = 4小的数。既然找第k小,那么找到一个小的,就算是完成了任务的一部分。

1、首先找a的中位数8.b的中位数2,发现8>2,说明合并后的中位数,在靠左一些的位置。这样以来,我们就找到了两个比较小的数,为b中2的左边(1,2),一共2个。这时问题就转化成:在a[7,8,9]和b[3,5]中找到第2小的数。

2、重复上面的过程,a中位数为8,b中位数为3,8>3,则说明合并后的中位数在8左边位置。b中3的左边(3),一共一个,问题转化成a[7,8,9]中和b[5]中找到第1小的数

3、还这么找,8>5,记录下5,发现5就是目前a[7,8,9],b[5]中第一小的数,则完成,并返回。

这里的折半体现在哪里呢?在上述描述中,折半直观上体现在b上:就统计小的个数后,就删去前半部分,只留后半部分,并且将寻找第k小变成寻找k-i小。这个在a中能不能体现呢?是不是8>3,那么8的右边就也可以删了呢?

这是不行的,假设问题是要找第8小,那么第一步进来8>2,统计2小,删去后b变成[3,5];一个很容易出错的思想就是,删除a的左边,删除b的右边,b变成[7,8],折半+折半多好。其实这个是不对的,因为我们发现,只有小的数才能影响我们的技术结果,找到i个,就只用再找k-i个就可以了。而至于那些大的,如果本身要找的k比较多,比如说k=8,要找第8小的,其实就是a中的9,如果b成了[7,8],就傻咯,找不到了。

所以折半和找第k小是分离的,折半找出一些小的数,让找第k小更容易一些。否则遍历的话,发现一个小的,计数器+1,这样是O(n)。

 

三、Python代码

1、O(m+n)方法:合并成一个序列求中位数

 1 class Solution:
 2     # @param {integer[]} nums1
 3     # @param {integer[]} nums2
 4     # @return {float}
 5     def findMedianSortedArrays(self, nums1, nums2):
 6         merge = nums1 + nums2
 7         merge.sort()
 8         m = len(nums1)
 9         n = len(nums2)
10         total = m + n
11         if total % 2 == 1:
12             return merge[total/2]
13         else:
14             return float(merge[total/2] + merge[total/2 - 1]) / 2

2、O(log(m+n))

 1 class Solution:
 2     # @param {integer[]} nums1
 3     # @param {integer[]} nums2
 4     # @return {float}
 5     def findkth(self, s, l, k):
 6         # s stands for short-list, l stands for long-list, and k stands for the kth smallest in the list.
 7         m = len(s)
 8         n = len(l)
 9         
10         # ensure the 1st list is short and the 2nd list is long
11         if m > n:
12             return self.findkth(l, s, k)
13         
14         #s is empty, so the kth small in l[k-1]
15         if not s:
16             return l[k - 1]
17             
18         #the last one small number, compare s[0] and l[0] cause the list is orderd.
19         if k == 1:
20             return min(s[0], l[0])
21     
22         #get the mid of the current list s and l
23         i = min(m, k / 2)
24         j = min(n, k / 2)
25     
26         #the s[mid] > l[mid], cut the small in l by using l[j:], and get update the kth: k - j
27         if s[i - 1] > l[j - 1]:
28             return self.findkth(s, l[j:], k - j)
29         else:
30             return self.findkth(s[i:], l, k - i)
31 
32 
33     def findMedianSortedArrays(self, nums1, nums2):
34         len_1 = len(nums1)
35         len_2 = len(nums2)
36         l = len_1 + len_2
37         rst = 0.0
38         
39         #even and odd
40         if l % 2 == 1:
41             rst = self.findkth(nums1, nums2, (l + 1) / 2) / 1.0
42         else:
43             rst = (self.findkth(nums1, nums2, (l + 1) / 2) + self.findkth(nums1, nums2, (l + 2) / 2)) / 2.0
44         
45         return rst

 

四、总结

1、要坚持把自己脑海中的东西说出来,这样一些自己一直有疑惑的问题写着写着就想明白了。

  之前我自己一直处于混沌状态,直到把这篇解题报告写完,才清楚了许多。之前一直什么搞不清楚呢,就是折半怎么用的问题。虽然大家都说a>b,则b的左边一半就不用看了,我也知道,但就是不知道为什么。写完这篇才知道,不用看的意思是,不用再去一个一个数左边到底有几个小的了。其实上面解析部分,讨论要不要去掉a中右边的一半,就是我一直疑惑的地方。

2、一定要抓住痛点,知道好的算法是要解决什么问题的。

  也是刚刚才想清楚本题用折半是用来做什么的。要找第k大,如何找?容易想到的有以下方法:

  1)从小到达排序,然后找第a[mid],排序复杂度为O(N),查找为O(1)

  2) 不排序,直接遍历找,设置一个最大值和计数器。发现一个比最大值大的,就更新最大值,计数器+1,复杂度O(n)

  要解决的问题是什么?要找第k大的值。疼点在什么?慢。那折半怎么就快了?一下能确定好多个。这不就结了!而不在于说形式上的,左边不要,只要右边。要知道不要以后,那些不要的数据去哪儿了。

3、写解题思路是很有用的,比如今天又用到了子函数,忘了调用,写成this.findkth,提交一直报错,看了之前的解题笔记,才发现是self.findkth,哈~~

 

posted @ 2015-07-14 16:41  面包包包包包包  阅读(134)  评论(0编辑  收藏  举报