题目
给定两个大小为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。
请你找出这两个正序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n))。
你可以假设 nums1 和 nums2 不会同时为空。
分析
单看题目寻找中位数而言,可以简化为寻找两个数列中的第n位数字,可以通过利用指针,对于两个数列中大小比较的,直至找到最中间的一个数或两个数,由于要求时间复杂度为O(log(m + n)),可以得知题主可能想使用要用到二分法寻找指定数,如果抛去时间复杂度这个附加条件,我们可以用一种比较简单和粗暴的解法来判断。
1.合并为一个数列
这个解法比较简单,将两个数列合并到一个数列中去,然后排序,若数列长度 m+n 为奇数,则 (n+m)/2 的位置即为中位数,若为偶数,则为中间两位数的和的二分之一,具体代码如下
func findMedianSortedArrays(nums1 []int, nums2 []int) float64 {
for i:=0; i<len(nums2); i++{
nums1 = append( nums1,nums2[i])
}
sort.Ints(nums1)
if (len(nums1)-1)%2 ==0{
return float64(nums1[(len(nums1)-1)/2])
}else{
return float64(nums1[(len(nums1)-1)/2] + nums1[(len(nums1))/2])/2
}
}`
在这个代码中,使用的是sort包,具体源代码如下,使用的是快速排序的方法
type Interface interface {
// Len is the number of elements in the collection.
Len() int
// Less reports whether the element with
// index i should sort before the element with index j.
Less(i, j int) bool
// Swap swaps the elements with indexes i and j.
Swap(i, j int)
}
/ Sort sorts data.
// It makes one call to data.Len to determine n, and O(n*log(n)) calls to
// data.Less and data.Swap. The sort is not guaranteed to be stable.
func Sort(data Interface) {
// Switch to heapsort if depth of 2*ceil(lg(n+1)) is reached.
n := data.Len()
maxDepth := 0
for i := n; i > 0; i >>= 1 {
maxDepth++
}
maxDepth *= 2
quickSort(data, 0, n, maxDepth)
}
所以用这种方法的时间复杂度为 O(m+n),空间复杂度也为 O(m+n),
2.不合并为一个数组,简化空间复杂度
直接两个数列进行比较,当比较出大小时,指针则向后移动一位,并将此值赋予输出值,这种方法麻烦在于,什么时候移动a组数列的指针,什么时候移动b组数列的指针,奇偶的时候如何划分,当数列总长度为奇数的时候,我们只需要取中间第k次的数值即可,当为偶数的时候,我们则需要上一次的输出结果,所以我们还需要一位数储存上一次的输出结果。
具体代码如下:
func findMedianSortedArrays(nums1 []int, nums2 []int) float64 {
n := len(nums1)
m := len(nums2)
s := n+m
a :=0
b :=0
outFinal :=0
out :=0
for i:=0; i<=s/2;i++{
outFinal=out
if a < n && (b >= m || nums1[a] < nums2[b]){
out = nums1[a]
a++
}else{
out = nums2[b]
b++
}
}
if s%2!=0 {
return float64(out)
} else{
return float64(out+outFinal)/2
}
}
这种方法同样繁琐,尤其是要考虑指针移动时,可能超出数列长度的问题,因为仍然经历了两个数列,所以时间复杂度依旧为O(m+n), 空间复杂度为O(1),因为只有几个特定值
3. 二分查找
二分查找可以大批量的解决掉运算复杂的问题,每一次的计算可以减下一半的计算量,在这道题中,可以简化为不断寻找中位数进行比较,具体原理如下。
首先,既然时间复杂度为O(log(m+n)),所以必然是将两个数列看做是同一个数列,再用二分法计算一半的量,假如有十一个数列,我们可以尝试分解他的解题思路。
首先,正如前面所说,本题的实质就是在找第k个最小数,则我们可以用二分法逐渐逼近第 k 小的数字来实现算法,则可以把两组数列中 K/2 个数字相比较,来去掉最小的两个数。我们可以直接比较A[K/2]和B[K/2],若A[K/2]<B[K/2],则之前的一定也小于中位数。
很多人可能疑惑,有没有可能中位数出现过于早,而后面的数字过于大,而过早的判定错误中位数,换句简单的话就是,有没有可能中位数在k/2之前就出现,这是不可能的,我们可以稍后再提。
如图,我们要找的是第6小的数字,比较可以直接,有3个数字应该小于中位数,即舍弃的是数列nums中的 nums1[1], nums1[2], nums1[3]。
然后将剩下的数组3,4, 5,6, 7,8, 9,10, 11放在一起,继续找第6小的数,因为已经删除了两位数字,所以只需要再删除三位数字就可以了,所以K=6-3=3,则 K/2=1,则有
红色部分为删除掉的数列,则删掉4,留下567891011,继续比较,K=2,K/2=1,则有

则6为第六小的数字,即中位数。
然后讨论有无可能中位数在 K/2之前,其实是不可能的,因为比中位数大的数字不可能出现在中位数之前,一旦中位数在数列中的次序提前,则后续数字也将增大,且一旦中位数的位置提前,大量比中位数小的数字会出现在另一数列中去,举例而言

把6向后移动一点
具体代码如下(两种):
func findMedianSortedArrays(nums1 []int, nums2 []int) float64 {
n:= len(nums1)
m:= len(nums2)
s:= len(nums1) + len(nums2)
if s%2 != 0 {
K := s/2
return float64(getKth(nums1, n,nums2, m,K+1))
} else {
K1, K2 := s/2-1 , s/2
return float64(getKth(nums1, n ,nums2,m, K1+1) + getKth(nums1,n,nums2,m, K2+1)) / 2.0
}
return 0
}
func getKth(nums1 []int, n int, nums2 []int, m int, k int) int {
var temp[] int
var test int
if n > m{
temp =nums1
nums1=nums2
nums2 = temp
test = n
n = m
m = test
}
if n == 0 {
return nums2[k-1]
}
if k == 1 {
return min(nums1[0], nums2[0])
}
i := min(k/2,n)
if nums1[i-1] > nums2[i-1] {
return getKth(nums1, n, nums2[i:], m-i, k-i)
} else {
return getKth(nums1[i:], n-i, nums2, m, k-i)
}
}
func min(x, y int) int {
if x < y {
return x
}
return y
}
func findMedianSortedArrays(nums1 []int, nums2 []int) float64 {
totalLength := len(nums1) + len(nums2)
if totalLength%2 == 1 {
midIndex := totalLength/2
return float64(getKthElement(nums1, nums2, midIndex + 1))
} else {
midIndex1, midIndex2 := totalLength/2 - 1, totalLength/2
return float64(getKthElement(nums1, nums2, midIndex1 + 1) + getKthElement(nums1, nums2, midIndex2 + 1)) / 2.0
}
return 0
}
func getKthElement(nums1, nums2 []int, k int) int {
index1, index2 := 0, 0
for {
if index1 == len(nums1) {
return nums2[index2 + k - 1]
}
if index2 == len(nums2) {
return nums1[index1 + k - 1]
}
if k == 1 {
return min(nums1[index1], nums2[index2])
}
half := k/2
newIndex1 := min(index1 + half, len(nums1)) - 1
newIndex2 := min(index2 + half, len(nums2)) - 1
pivot1, pivot2 := nums1[newIndex1], nums2[newIndex2]
if pivot1 <= pivot2 {
k -= (newIndex1 - index1 + 1)
index1 = newIndex1 + 1
} else {
k -= (newIndex2 - index2 + 1)
index2 = newIndex2 + 1
}
}
return 0
}
func min(x, y int) int {
if x < y {
return x
}
return y
}
时间复杂度为O(log(m+n)),也可以尝试两个数列分别取中位数然后比较的方法,时间复杂度为O(logm*n)。
该博客介绍了如何在O(log(m+n))的时间复杂度内找到两个正序数组的中位数。通过分析题目,提出了三种解法:1) 合并数组并排序;2) 不合并数组,直接比较;3) 使用二分查找法。重点讨论了二分查找法的思路,确保不会过早判断中位数,并提供了相关代码示例。
8万+

被折叠的 条评论
为什么被折叠?



