寻找两个有序数组的中位数-leetcode
Mar 17, 2019
题目
两个有序数组,如何在时间复杂度为O(log(m+n))
的条件下寻找到将其有序合并后数组的中位数?
这道题的最优解有三个关键点:
- 意识到寻找中间点,就是将有序数组拆分为等长的两段,拆分点就是中间点。所以对于两个有序数组,寻找他们合起来的中间点就是分别将两个数组拆分成两段,左边段加起来的长度等于右边段的长度,这样能保证是中间位置;其次保证左边段的末位断点小于右边段的起始端点,则保证了数组合并的拆分点是有序数组的中间点。
- 要将分支条件梳理清楚。如满足题意的中间点的条件实际是
(i=0 || j=n || A[i-1]<=B[j])&&(j=0 || i=m || B[j-1]<=A[j])
// m<=n。我可以想到要保证端点值大小约束的条件,但对于边界条件如 i=0,i=m… 如何融入进去没有思考清楚。没有思考清楚的原因是也没有去真正细致地把这些条件写出来,而是一想到有这么多边界条件就嫌麻烦,想找捷径;而这里却是真正把所有问题列出来,写写画画,才能把问题看得更清楚的必须路径。即便有麻烦的边界,还是依次把其写出来,去对比思考,才更容易看清楚它的本质。- 当有了上述条件,就可以找到它的反条件。把 || 和 && 想成数学上的集合的并与交,即可以推导出它的反条件:
!((i=0 || j=n || A[i-1]<=B[j])&&(j=0 || i=m || B[j-1]<=A[j])) <==> (!(i=0 || j=n || A[i-1]<=B[j]) || !(j=0 || i=m || B[j-1]<=A[j])) <==> ((!(i=0) && !(j=n) && !(A[i-1]<=B[j])) || (!(j=0) && !(i=m) && !(B[j-1]<=A[j]))) <==> ((i>0 && j<n && A[i-1]>B[j]) || (j>0 && i<m && B[j-1]>A[j]))
- 注意上述推导中最后一步,取
i=0
的非时,因为i不会小于0,所以直接为i>0
。 - 至此,就可以将整个算法的分支写为:
if(i>0 && j<n && A[i-1]>B[j])
else if(j>0 && i<m && B[j-1]>A[j])
else{// 找到了匹配的i,j,但i,j可能为边界值。没有关系,只要在计算中位值时注意就可以了}
- 至此,算法的逻辑已经很清晰了。
- 当有了上述条件,就可以找到它的反条件。把 || 和 && 想成数学上的集合的并与交,即可以推导出它的反条件:
- 最后一点是,在
m<=n
的条件中,i>0
与j<n
是等价的。如此前述2.3.1和2.3.2中的条件都是可以简写的。具体推导就不写了。关键在于充分利用前提条件m<=n
,而自己就忽略了这个条件。
代码
1 | function findMedian2(nums1, nums2) { |
自己的初始思路
自己也想出了一个思路,只是代码写起来非常复杂和容易出错,且算法复杂度应该是 O(log(m)log(n))
的。不过自己也花了很大的心思写出来了,还是值得记录一下的。
思路就是,在两个数组出现交叉时,寻找开始端点较大者在对方数组的插入位置:
- 若此位置大于所要的中间位数(它是固定的),则中间点必在对方数组中,根据所要中位数下标即可找到
- 若此位置等于所要的中间位数,则他就是要找的中间点,再通过比较两个数组的下一个节点大小,即可找到
- 若此位置小于所要的中间位数,则取对方数组的(所找到的插入位置的下一个)元素为待查找元素,反过来在自己的数组中,从刚才的查找元素之后开始查找,再取比较其相对位置与中位数所需的相对位置的偏差,即进入下一个循环。
这样如果在查找时用二分查找,那么效率是 O(log(searchlength))
的。而进行查找的个数,因为是对方分片查找导致的,也可以认为是 O(log(length))
。所以可以视整体复杂度为 O(log(m)log(n))
。
这道题自己可以把自己的思路坚持写出来,并通过测试集,击败将近 50% 的同语言算法,还是不错的。
1 | var findMedianSortedArrays = function(nums1, nums2) { |