LeetCode第四题: 寻找两个正序数组的中位数

简介: 给定两个大小分别为m和n的正序(从小到大)数组nums1和nums2。请你找出并返回这两个正序数组的中位数。

题目描述

  给定两个大小分别为m和n的正序(从小到大)数组nums1和nums2。请你找出并返回这两个正序数组的中位数。

示例

nums1 = [1, 3]
nums2 = [2]
则中位数是 2.0
nums1 = [1, 2]
nums2 = [3, 4]
则中位数是 (2 + 3)/2 = 2.5

解题思路 - 二分查找法

  我们可以使用二分查找法来解决这个问题。关键在于找到两个数组中第k小的数,其中k是两个数组长度之和的一半。如果两个数组的长度之和是奇数,那么中位数就是第k小的数;如果是偶数,那么中位数是第k小和第k+1小的数的平均值。
我们可以分别在两个数组中进行二分查找,每次比较两个数组中第k/2个元素的大小,然后根据比较结果排除掉一定不可能包含中位数的部分,从而缩小查找范围。

Go语言实现 - 二分查找法

  下面是使用Go语言实现的代码:

func findMedianSortedArrays(nums1 []int, nums2 []int) float64 {
   
   
    totalLength := len(nums1) + len(nums2)
    if totalLength%2 == 1 {
   
   
        return float64(findKthElement(nums1, nums2, totalLength/2+1))
    } else {
   
   
        left := findKthElement(nums1, nums2, totalLength/2)
        right := findKthElement(nums1, nums2, totalLength/2+1)
        return float64(left+right) / 2.0
    }
}
func findKthElement(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
        }
    }
}
func min(x, y int) int {
   
   
    if x < y {
   
   
        return x
    }
    return y
}

算法分析

  • 时间复杂度: O(log(min(m, n))),因为我们每次都会排除掉一定比例的元素。
  • 空间复杂度: O(1),因为我们只需要常数级别的额外空间来存储索引和临时变量。

  ​image

合并数组法

  最直接的方法是将两个数组合并成一个有序数组,然后根据合并后数组的长度是奇数还是偶数,找到中位数。这种方法的时间复杂度是O(m+n),因为合并两个数组需要遍历所有元素。

Go语言实现 - 合并数组法

func findMedianSortedArrays(nums1 []int, nums2 []int) float64 {
   
   
    merged := mergeSortedArrays(nums1, nums2)
    n := len(merged)
    if n%2 == 1 {
   
   
        return float64(merged[n/2])
    }
    return float64(merged[n/2-1]+merged[n/2]) / 2.0
}
func mergeSortedArrays(nums1, nums2 []int) []int {
   
   
    merged := make([]int, 0, len(nums1)+len(nums2))
    i, j := 0, 0
    for i < len(nums1) && j < len(nums2) {
   
   
        if nums1[i] < nums2[j] {
   
   
            merged = append(merged, nums1[i])
            i++
        } else {
   
   
            merged = append(merged, nums2[j])
            j++
        }
    }
    for ; i < len(nums1); i++ {
   
   
        merged = append(merged, nums1[i])
    }
    for ; j < len(nums2); j++ {
   
   
        merged = append(merged, nums2[j])
    }
    return merged
}

算法分析

  • 时间复杂度: O(m+n),因为需要合并两个数组。
  • 空间复杂度: O(m+n),因为需要额外的空间来存储合并后的数组。

  ​image

双指针法

  另一种方法是使用双指针,分别遍历两个数组。这种方法不需要合并数组,而是通过指针来找到中位数。

Go语言实现 - 双指针法

func findMedianSortedArrays(nums1 []int, nums2 []int) float64 {
   
   
    m, n := len(nums1), len(nums2)
    left, right := (m+n+1)/2, (m+n+2)/2
    return float64(findKth(nums1, 0, nums2, 0, left)+findKth(nums1, 0, nums2, 0, right)) / 2.0
}
func findKth(nums1 []int, i int, nums2 []int, j int, k int) int {
   
   
    if i >= len(nums1) {
   
   
        return nums2[j+k-1]
    }
    if j >= len(nums2) {
   
   
        return nums1[i+k-1]
    }
    if k == 1 {
   
   
        return min(nums1[i], nums2[j])
    }
    midVal1, midVal2 := math.MaxInt32, math.MaxInt32
    if i+k/2-1 < len(nums1) {
   
   
        midVal1 = nums1[i+k/2-1]
    }
    if j+k/2-1 < len(nums2) {
   
   
        midVal2 = nums2[j+k/2-1]
    }
    if midVal1 < midVal2 {
   
   
        return findKth(nums1, i+k/2, nums2, j, k-k/2)
    }
    return findKth(nums1, i, nums2, j+k/2, k-k/2)
}
func min(x, y int) int {
   
   
    if x < y {
   
   
        return x
    }
    return y
}

算法分析

  • 时间复杂度: O(m+n),在最坏的情况下,每次递归都会排除掉一个元素。
  • 空间复杂度: O(1),因为不需要额外的空间来存储合并后的数组。
    在这些解法中,二分查找法通常是最优的,因为它的时间复杂度是O(log(min(m, n))),而其他方法的时间复杂度是O(m+n)。

  ​image

相关文章
|
1月前
|
算法
Leetcode 初级算法 --- 数组篇
Leetcode 初级算法 --- 数组篇
38 0
|
3月前
|
算法
LeetCode第53题最大子数组和
LeetCode第53题"最大子数组和"的解题方法,利用动态规划思想,通过一次遍历数组,维护到当前元素为止的最大子数组和,有效避免了复杂度更高的暴力解法。
LeetCode第53题最大子数组和
|
3月前
|
存储 Java API
LeetCode------合并两个有序数组(4)【数组】
这篇文章介绍了LeetCode上的"合并两个有序数组"问题,并提供了三种解法:第一种是使用Java的Arrays.sort()方法直接对合并后的数组进行排序;第二种是使用辅助数组和双指针技术进行合并;第三种则是从后向前的双指针方法,避免了使用额外的辅助数组。
LeetCode------合并两个有序数组(4)【数组】
|
3月前
|
搜索推荐 索引 Python
【Leetcode刷题Python】牛客. 数组中未出现的最小正整数
本文介绍了牛客网题目"数组中未出现的最小正整数"的解法,提供了一种满足O(n)时间复杂度和O(1)空间复杂度要求的原地排序算法,并给出了Python实现代码。
113 2
|
3月前
|
Python
【Leetcode刷题Python】剑指 Offer 03. 数组中重复的数字
解决剑指Offer题目 "数组中重复的数字" 的Python实现方法,通过使用字典来记录数组中每个数字的出现次数,快速找出重复的数字。
37 1
LeetCode------找到所有数组中消失的数字(6)【数组】
这篇文章介绍了LeetCode上的"找到所有数组中消失的数字"问题,提供了一种解法,通过两次遍历来找出所有未在数组中出现的数字:第一次遍历将数组中的每个数字对应位置的值增加数组长度,第二次遍历找出所有未被增加的数字,即缺失的数字。
|
3月前
|
前端开发
LeetCode------移动零(5)【数组】
这篇文章介绍了LeetCode上的"移动零"问题,提出了一种使用双指针的原地操作解法,该方法首先将非零元素移动到数组前端并保持相对顺序,然后填充后续位置为零,以达到题目要求。
|
1月前
【LeetCode-每日一题】 删除排序数组中的重复项
【LeetCode-每日一题】 删除排序数组中的重复项
19 4
|
1月前
|
索引
Leetcode第三十三题(搜索旋转排序数组)
这篇文章介绍了解决LeetCode第33题“搜索旋转排序数组”的方法,该问题要求在旋转过的升序数组中找到给定目标值的索引,如果存在则返回索引,否则返回-1,文章提供了一个时间复杂度为O(logn)的二分搜索算法实现。
18 0
Leetcode第三十三题(搜索旋转排序数组)
|
1月前
|
算法 C++
Leetcode第53题(最大子数组和)
这篇文章介绍了LeetCode第53题“最大子数组和”的动态规划解法,提供了详细的状态转移方程和C++代码实现,并讨论了其他算法如贪心、分治、改进动态规划和分块累计法。
56 0