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

相关文章
|
9天前
|
C++ Python
二刷力扣--数组
二刷力扣--数组
|
14天前
|
存储 SQL 算法
LeetCode第53题:最大子数组和【python 5种算法】
LeetCode第53题:最大子数组和【python 5种算法】
|
10天前
|
索引
【LeetCode刷题】二分查找:山脉数组的峰顶索引、寻找峰值
【LeetCode刷题】二分查找:山脉数组的峰顶索引、寻找峰值
|
11天前
|
算法
【经典LeetCode算法题目专栏分类】【第10期】排序问题、股票问题与TOP K问题:翻转对、买卖股票最佳时机、数组中第K个最大/最小元素
【经典LeetCode算法题目专栏分类】【第10期】排序问题、股票问题与TOP K问题:翻转对、买卖股票最佳时机、数组中第K个最大/最小元素
|
14天前
|
存储 算法 数据可视化
深入解读力扣154题:寻找旋转排序数组中的最小值 II(多种方法及详细ASCII图解)
深入解读力扣154题:寻找旋转排序数组中的最小值 II(多种方法及详细ASCII图解)
|
14天前
|
存储 算法 数据可视化
|
14天前
|
存储 传感器 算法
LeetCode题目89:格雷码 递归、迭代及位操作在数组合并中的应用
LeetCode题目89:格雷码 递归、迭代及位操作在数组合并中的应用
|
18天前
|
Java
贪心 -力扣860.柠檬水找零力扣2208.将数组和减半的最少操作次数力扣179.最大数力扣376.摆动序列
贪心 -力扣860.柠檬水找零力扣2208.将数组和减半的最少操作次数力扣179.最大数力扣376.摆动序列
|
19天前
|
存储 算法 Java
【经典算法】 leetcode88.合并排序的数组(Java/C/Python3实现含注释说明,Easy)
【经典算法】 leetcode88.合并排序的数组(Java/C/Python3实现含注释说明,Easy)
12 1
|
10天前
【LeetCode刷题】前缀和解决问题:742.寻找数组的中心下标、238.除自身以外数组的乘积
【LeetCode刷题】前缀和解决问题:742.寻找数组的中心下标、238.除自身以外数组的乘积