鱼C论坛

 找回密码
 立即注册
查看: 100|回复: 5

[技术交流] 4. 寻找两个正序数组的中位数

[复制链接]
发表于 3 天前 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能^_^

您需要 登录 才可以下载或查看,没有账号?立即注册

x
本帖最后由 Gnomeshgh 于 2025-8-21 15:57 编辑

一、题目描述


wechat_2025-08-21_155357_561.png


二、解题思路


(一)暴力解



很明显,两个列表都有序,最简单的思路就是合成一个列表然后取中位数。
直接用列表加法进行合并,然后再对合并后的列表进行排序。最后取中位数。


  1. class Solution:
  2.     def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
  3.         merged = nums1 + nums2
  4.         # 排序
  5.         merged.sort()
  6.         length = len(merged)
  7.         mid = length // 2  # 利用整数除法直接获取中间索引
  8.         
  9.         if length % 2 == 1:
  10.             return float(merged[mid])
  11.         else:
  12.             return (merged[mid - 1] + merged[mid]) / 2.0
复制代码


进行优化,上面的思路需要合并,但是两个列表长度已知,也就是其实可以计算出中位数在第几个,只需要移动到中位数既可。
可以用两个指针指向两个列表,然后谁小谁移动,保存没有移动的数据,直到移动到中位数。
对于偶数而言还需要保存它的左边的数据,也就是在移动前先保存。


  1. class Solution:
  2.     def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
  3.         # 计算两个数组的长度
  4.         nums1_len , nums2_len = len(nums1) , len(nums2)
  5.         # 计算两个数组的总长度
  6.         total =  nums1_len + nums2_len
  7.         # 初始化两个数组的指针,分别指向起始位置(0索引)
  8.         nums1_p = nums2_p = 0
  9.         # 用于存储中位数相关的两个元素:
  10.         # right存储当前遍历到的元素,left存储上一次遍历到的元素
  11.         right = left = 0

  12.         # 遍历到中位数位置:总长度//2 + 1次
  13.         # 例如总长度5,需要遍历3次(0,1,2位置);总长度4,需要遍历3次(0,1,2位置)
  14.         for _ in range(total // 2 + 1):
  15.             # 将当前right的值保存到left(为偶数长度时计算两个中间值的平均做准备)
  16.             left = right
  17.             
  18.             # 指针移动逻辑:
  19.             # 1. 确保nums1指针未越界
  20.             # 2. 两种情况需要移动nums1指针:
  21.             #    a. nums2指针已越界(nums2遍历完毕)
  22.             #    b. nums1当前元素小于nums2当前元素(取较小值推进)
  23.             if nums1_p < nums1_len and (nums2_p >= nums2_len or nums1[nums1_p] < nums2[nums2_p]):
  24.                 # 更新right为nums1当前元素
  25.                 right = nums1[nums1_p]
  26.                 # 移动nums1指针到下一位
  27.                 nums1_p = nums1_p + 1
  28.             else:
  29.                 # 否则移动nums2指针(nums2未遍历完且当前元素更小)
  30.                 right = nums2[nums2_p]
  31.                 # 移动nums2指针到下一位
  32.                 nums2_p = nums2_p + 1
  33.         
  34.         # 根据总长度奇偶性返回结果:
  35.         # - 偶数:返回中间两个元素的平均值(left和right)
  36.         # - 奇数:返回中间元素(right,因为遍历到了中间位置)
  37.         return (left + right) / 2.0 if total % 2 == 0 else float(right)
复制代码



(二)二分法


一般对于有序数组而言会优先想到二分法。
题目求中位数,也就是划分了左右两部分,当是偶数时,左右两部分相等,奇数时,这里让左半部分多1.
也就是在两个数组中找到分割的位置,
假设nums1的分割点为i,(左边有i个,右边有m-i个)
nums2的分割点为j,(左边有j个,右边有n-j个)
当奇数时,即左边最大值就是中间值,此时左半部分是比右半部分多1的
假设总长度为5,则左半部分为3,右半部分为2,此时(m+n+1)// 2 = 3
当为偶数时,左边最大与右边最小之和的二分之一就是中值。
假设总长度为4,则左半部分右半部分都为2,此时(m+n+1)// 2 = 2
所以左半部分总长度的公式为(m+n+1)// 2

wechat_2025-08-21_124756_196.png


因为前半部分是[0,i-1]和[0,j-1]所以,nums1[i-1]<nums2[j],nums2[j-1] < nums1
由于已知i就能求出j,所以只需要调整一个就能实现调整两个,
当nums2[j-1] > nums1不符合时,也就是左半部分不全小于右半部分,也就是i要增大,也就是left变大,又因为left是闭合的,所以left = i + 1
其它情况就是right变小,又因为right是开区间,所以right = i
然后不断的向中值逼近,直到退出循环找到最佳的中值位置

  1. class Solution:
  2.     def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
  3.         # 1. 确保在较短的数组上进行二分查找,减少二分次数,优化时间复杂度
  4.         # 因为二分查找的时间复杂度取决于数组长度,短数组的log值更小
  5.         if len(nums1) > len(nums2):
  6.             nums1, nums2 = nums2, nums1
  7.         
  8.         # 2. 定义基本变量
  9.         m, n = len(nums1), len(nums2)  # m为较短数组长度,n为较长数组长度
  10.         left, right = 0, m  # 二分查找的边界:nums1的分割点i的可能范围(0~m)
  11.         # 左半部分总长度:确保左半元素数量 ≥ 右半(奇数时多1,偶数时相等)
  12.         total_left_length = (m + n + 1) // 2  # 公式统一奇数和偶数情况

  13.         # 3. 二分查找寻找最优分割点i
  14.         # 目标:找到i使得nums1和nums2的左半部分所有元素 ≤ 右半部分所有元素
  15.         while left < right:
  16.             # 计算当前分割点i(向下取整,配合后续逻辑避免死循环)
  17.             i = (left + right) // 2
  18.             # 计算nums2的分割点j:左半总长度固定,j由i决定
  19.             j = total_left_length - i

  20.             # 判断当前i是否需要右移:
  21.             # nums1[i]是nums1右半的最小值,nums2[j-1]是nums2左半的最大值
  22.             # 若nums1右半最小值 < nums2左半最大值,说明i太小(nums1左半需包含更多元素)
  23.             if nums1[i] < nums2[j - 1]:
  24.                 left = i + 1  # 右移i,同时保证范围缩小(避免死循环)
  25.             else:
  26.                 # 否则i可能过大,左移分割点
  27.                 right = i
  28.         
  29.         # 4. 确定最终分割点i和j
  30.         i , j = left , total_left_length - left

  31.         # 5. 计算左半部分的最大值(中位数的左边界)
  32.         # 处理边界情况:某一数组的左半部分为空
  33.         if i == 0:
  34.             # nums1左半为空,左半最大值只能来自nums2的左半
  35.             max_left = nums2[j - 1]
  36.         elif j == 0:
  37.             # nums2左半为空,左半最大值只能来自nums1的左半
  38.             max_left = nums1[i - 1]
  39.         else:
  40.             # 正常情况:取两个数组左半部分的最大值
  41.             max_left = max(nums1[i - 1], nums2[j - 1])
  42.         
  43.         # 6. 若总长度为奇数,中位数就是左半部分的最大值(左半比右半多1个元素)
  44.         if (m + n ) % 2 == 1:
  45.             return float(max_left)  
  46.         
  47.         # 7. 若总长度为偶数,还需计算右半部分的最小值(中位数的右边界)
  48.         # 处理边界情况:某一数组的右半部分为空
  49.         if i == m:
  50.             # nums1右半为空,右半最小值只能来自nums2的右半
  51.             min_right = nums2[j]
  52.         elif j == n:
  53.             # nums2右半为空,右半最小值只能来自nums1的右半
  54.             min_right = nums1[i]
  55.         else:
  56.             # 正常情况:取两个数组右半部分的最小值
  57.             min_right = min(nums1[i], nums2[j])

  58.         # 8. 偶数长度时,中位数是左半最大值和右半最小值的平均值
  59.         return (max_left + min_right) / 2
复制代码



评分

参与人数 1荣誉 +2 鱼币 +3 贡献 +3 C币 +3 收起 理由
不二如是 + 2 + 3 + 3 + 3 真手写教程

查看全部评分

本帖被以下淘专辑推荐:

小甲鱼最新课程 -> https://ilovefishc.com
回复

使用道具 举报

发表于 3 天前 | 显示全部楼层
为了找到两个正序数组的中位数并要求时间复杂度为 O(log(m+n)),我们可以使用二分查找的方法。核心思想是将两个数组分割成左右两部分,使得左半部分的所有元素都小于或等于右半部分的所有元素,然后根据总长度的奇偶性计算中位数。

### 方法思路
1. **确保 nums1 是较短的数组**:这样二分查找的范围更小,效率更高。
2. **计算总长度 total**:total = m + n。
3. **确定左半部分的大小**:left_size = (total + 1) // 2(向上取整)。
4. **在 nums1 上使用二分查找**:找到分割点 i,使得 nums1 的左半部分和 nums2 的左半部分合并后,左半部分的最大值小于等于右半部分的最小值。
5. **调整分割点**:通过比较 nums1[i-1] 和 nums2[j] 以及 nums2[j-1] 和 nums1[i] 来调整 i。
6. **计算中位数**:根据总长度的奇偶性,取左半部分的最大值和右半部分的最小值(或平均值)。

### 解决代码

  1. def findMedianSortedArrays(nums1, nums2):
  2.     # 确保 nums1 是较短的数组
  3.     if len(nums1) > len(nums2):
  4.         nums1, nums2 = nums2, nums1
  5.         
  6.     m, n = len(nums1), len(nums2)
  7.     total = m + n
  8.     left_size = (total + 1) // 2  # 左半部分的大小(向上取整)
  9.    
  10.     # 在 nums1 上使用二分查找
  11.     low, high = 0, m
  12.     while low <= high:
  13.         i = (low + high) // 2  # nums1 的分割点
  14.         j = left_size - i       # nums2 的分割点
  15.         
  16.         # 处理边界情况
  17.         nums1_left_max = float('-inf') if i == 0 else nums1[i-1]
  18.         nums1_right_min = float('inf') if i == m else nums1[i]
  19.         nums2_left_max = float('-inf') if j == 0 else nums2[j-1]
  20.         nums2_right_min = float('inf') if j == n else nums2[j]
  21.         
  22.         # 检查分割是否正确
  23.         if nums1_left_max <= nums2_right_min and nums2_left_max <= nums1_right_min:
  24.             # 分割正确,计算中位数
  25.             if total % 2 == 1:
  26.                 return max(nums1_left_max, nums2_left_max)
  27.             else:
  28.                 return (max(nums1_left_max, nums2_left_max) + min(nums1_right_min, nums2_right_min)) / 2.0
  29.         elif nums1_left_max > nums2_right_min:
  30.             high = i - 1  # i 太大,减小 i
  31.         else:
  32.             low = i + 1   # i 太小,增大 i
  33.             
  34.     return 0.0  # 不会执行到这里,但为了语法完整
复制代码


### 代码解释
- **确保 nums1 较短**:交换数组以使二分查找更高效。
- **计算 left_size**:左半部分的大小为总长度加一后除以二(向上取整)。
- **二分查找分割点 i**:在 nums1 上调整 i,使得左半部分的最大值小于等于右半部分的最小值。
- **处理边界**:当 i 或 j 为 0 或数组长度时,使用无穷大或负无穷大避免索引错误。
- **计算中位数**:根据总长度奇偶性,返回左半部分最大值或左右部分最大最小值的平均值。

这种方法确保了时间复杂度为 O(log(min(m, n))),符合要求。

以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 3 天前 | 显示全部楼层
小甲鱼最新课程 -> https://ilovefishc.com
回复

使用道具 举报

发表于 3 天前 From FishC Mobile | 显示全部楼层
可以哇,学习了
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 前天 08:39 | 显示全部楼层
看不懂
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 前天 19:26 | 显示全部楼层

哪里看不懂,你说说,我在改一改
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2025-8-24 23:05

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表