给定两个大小为 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
题解:
如果不考虑时间复杂度的话,最简单的做法,就是将nums1和nums2合并成一个数组并进行排序,然后取该数组的中位数,但是哪怕是快速排序,时间也为O(nlogn),明显与题目要求的O(log(m+n))不相符。所以,我们要另辟蹊径。而题目除了给出的两个数组,同时还告诉我们,这两个数组,是有序数组,这使得我们可以利用有序,在O(log(m+n))的时间复杂度内,寻得中位数。
我们要知道,中位数的作用,是将数组的两端分成两个等长的片段,如:1 2 |3| 4 5或1 2 |3 4| 5 6,符号|将原数组除中位数外,分割成两个等长的片段。那么,现在我们有两个数组:num1: [a1,a2,a3,...an]和nums2: [b1,b2,b3,...bm],我们用|来分割数组[nums1[:i],nums2[:j] | nums1[i:], nums2[j:]],只要能保证经由|分割出来的两个片段是等长的,中位数就在|这个边界产生。
首先,我们来做一个假设,我们假设nums1的长度永远小于或者等于nums2,那么,nums2至少包含一个中位数。其次,我们假定nums3是nums1和nums2合并后并按照顺序排列,如果len(nums3)的长度为奇数,那么中位数的位置则为(len(nums3)+1)/2,如果len(nums3)为偶数,那么中位数的位置有两个,len(nums3)/2和(len(nums3)/2)+1,而在计算机的世界里,任何整数除以2,都会向下取整,比如整数6和7除以2的结果都是3,为了方便表示,我们统一认为中位数会在(len(nums3)+1)/2的位置上出现,即(len(nums1)+len(nums2)+1)/2。
比如:
nums3 = [1,2,3,4,5,6,7],那么中位数处于m=(7+1)/2=4
或者nums3=[1,2,3,4,5,6],那么中位数处于m1=(6+1)/2=3,m2=m1+1=4
我们看下面的表格,我们假定nums1的索引i和nums2的索引j处于|的两端,len(left)=len(right)且max(ai,bj)<=min(ai+1,bj+1)。
left | right |
[a1,a2……ai],[b1,b2,……bj] | [ai+1,……an],[bj+1,……bm] |
如果我们能确定i在nums1中的位置,那么同样可以确定j在nums2的位置,j=(len(nums1)+len(nums2)+1/2)-i。
现在,我们就可以开始着手写程序了:
//根据index获取nums中的值,如果index无效,则返回defaultVal func GetValWithDefault(nums []int, index int, defaultVal int) int { if index >= 0 && index < len(nums) { return nums[index] } return defaultVal } func findMedianSortedArrays(nums1 []int, nums2 []int) float64 { if len(nums1) > len(nums2) { //<1>如果nums1的长度大于nums2,则替换引用,确保nums2中至少存在一个中位数 nums1, nums2 = nums2, nums1 } l1, l2 := len(nums1), len(nums2) left, right := 0, l1 m := (l1 + l2 + 1) / 2 for left < right { i := left + (right-left)/2 //<2>寻找i和j,使得[nums1[:i],nums2[:j] | nums1[i:], nums2[j:]]能将两个数组切割成两个等长的片段 j := m - i if nums1[i] < nums2[j-1] { left = i + 1 } else { right = i } } right = m - left //<3>定位到j的位置 maxLeft := math.Max( float64(GetValWithDefault(nums1, left-1, math.MinInt32)), float64(GetValWithDefault(nums2, right-1, math.MinInt32)), ) if (l1+l2)%2 == 1 { return maxLeft } minRight := math.Min( float64(GetValWithDefault(nums1, left, math.MaxInt32)), float64(GetValWithDefault(nums2, right, math.MaxInt32)), ) return (maxLeft + minRight) / 2 }
按照上述代码,我们可以将时间复杂度控制在log(min(m,n))之内。