当前位置: 首页 > news >正文

4.寻找两个正序数组的中位数-二分查找

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

算法的时间复杂度应该为 O(log (m+n)) 。(暗示我们用二分法

示例 1:

输入:nums1 = [1,3], nums2 = [2]
输出:2.00000
解释:合并数组 = [1,2,3] ,中位数 2

示例 2:

输入:nums1 = [1,2], nums2 = [3,4]
输出:2.50000
解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5

当 m+n 是奇数时,中位数是两个有序数组中的第 (m+n)/2 个元素

当 m+n 是偶数时,中位数是两个有序数组中的第 (m+n)/2 个元素和第 (m+n)/2+1 个元素的平均值

方法1:暴力解法,先合并两个数组,然后排序找到中位数

方法2: 合并两个有序数组,逐个放入两个数组中最小的数子,得到排序后的合并数组,计算中位数。

方法3:基于中位数的统计意义,找到一条横跨两个数组的分割线,位于分割线左边的数组都小于分割线右边的数字。那么,中位数只与分割线左右两边的4个数字有关。

方法4:基于中位数的计算,找到第 X 个小的数字,根据数组长度总和是否为偶数,查找1至2个数字。

解题思路

方法1:暴力解法

先合并两个数组,然后排序找到中位数。但这种解法没有用到数组有序这一条件。

时间复杂度:O((m+n)log(m+n)),这是快速排序的时间复杂度。

空间复杂度:O(m+n),储存合并之后的数组。

排序类别排序算法时间复杂度(最好)时间复杂度(最坏)时间复杂度(平均)辅助空间复杂度稳定性
插入排序直接插入排序O(n)o(n^{2})o(n^{2})O(1)稳定
折半插入排序O(n)o(n^{2})o(n^{2})O(1)稳定
希尔排序O(n)o(n^{2})o(n^{1.3})O(1)不稳定
交换排序冒泡排序O(n)o(n^{2})o(n^{2})O(1)稳定
快速排序O(nlog_{2}n)o(n^{2})O(nlog_{2}n)O(log_{2}n)不稳定

快速排序算法

任取一元素作为枢轴,左边的小于等于枢轴,右半部分大于等于枢轴。然后递归处理,直到为空或只剩一个。

推荐视频:【数据结构合集 - 快速排序(算法过程, 效率分析, 稳定性分析)】https://www.bilibili.com/video/BV1y4421Z7hK?vd_source=e4058354e05bcd196be50cc59d6df825

辅助数组法

这种快速排序实现方式使用一个辅助数组来存储划分后的结果,相比原地排序更直观易懂,但会使用额外的O(n)空间。

完整代码:

#include <iostream>
#include <vector>using namespace std;void quickSortWithAux(vector<int>& arr, vector<int>& aux, int low, int high) {if (low >= high) return;  // 递归终止条件int pivot = arr[high];    // 选择最后一个元素作为基准int left = low;           // 左指针 - 用于小于pivot的元素int right = high;         // 右指针 - 用于大于等于pivot的元素// 将划分结果存入辅助数组for (int i = low; i < high; i++) {if (arr[i] < pivot) {aux[left++] = arr[i];  // 小于基准的放左边} else {aux[right--] = arr[i];  // 大于等于基准的放右边}}aux[left] = pivot;  // 放置基准值// 将辅助数组的结果复制回原数组for (int i = low; i <= high; i++) {arr[i] = aux[i];}// 递归排序左右子数组quickSortWithAux(arr, aux, low, left - 1);quickSortWithAux(arr, aux, left + 1, high);
}void quickSort(vector<int>& arr) {if (arr.size() <= 1) return;vector<int> aux(arr.size());  // 创建辅助数组quickSortWithAux(arr, aux, 0, arr.size() - 1);
}

分区演示

//low=0, high=4
原数组: [3, 1, 4, 2, 5] (pivot=5)
分区过程:
aux数组变化:
[3, 0, 0, 0, 0]   // 3 < 5 → 放左边
[3, 1, 0, 0, 0]   // 1 < 5 → 放左边
[3, 1, 0, 4, 0]   // 4 < 5 → 放左边
[3, 1, 2, 4, 0]   // 2 < 5 → 放左边
最终:
[3, 1, 2, 4, 5]   // 放入基准值
//left=4, right=4// 排序左半部分(小于基准的部分)
quickSortWithAux(arr, aux, 0, 3);// 排序右半部分(大于基准的部分) 
quickSortWithAux(arr, aux, 5, 4);

原地操作法
#include <iostream>
#include <vector>using namespace std;int partition(vector<int>& arr, int low, int high) {int pivot = arr[high];    // 选择最后一个元素作为基准int i = low - 1;          // 指向小于基准的区域的末尾for (int j = low; j < high; j++) {if (arr[j] < pivot) {i++;swap(arr[i], arr[j]);  // 将小于基准的元素交换到前面}}swap(arr[i + 1], arr[high]);  // 将基准放到正确位置return i + 1;                 // 返回基准位置
}void quickSort(vector<int>& arr, int low, int high) {if (low < high) {// 划分数组并获取基准位置int pivotIndex = partition(arr, low, high);// 递归排序左右子数组quickSort(arr, low, pivotIndex - 1);quickSort(arr, pivotIndex + 1, high);}
}void quickSort(vector<int>& arr) {if (arr.size() <= 1) return;quickSort(arr, 0, arr.size() - 1);
}

避免最坏情况

避免取到最小值和最大值,为了避免最坏情况,可以使用随机化选择基准。

#include <cstdlib>
#include <ctime>int randomPartition(vector<int>& arr, int low, int high) {// 随机选择基准srand(time(NULL));int randomIndex = low + rand() % (high - low + 1);swap(arr[randomIndex], arr[high]);  // 将随机选择的基准换到最后return partition(arr, low, high);   // 使用相同的划分逻辑
}void quickSortRandom(vector<int>& arr, int low, int high) {if (low < high) {int pivotIndex = randomPartition(arr, low, high);quickSortRandom(arr, low, pivotIndex - 1);quickSortRandom(arr, pivotIndex + 1, high);}
}

方法2: 合并两个有序数组

借鉴归并排序的合并步骤:

  1. 使用双指针分别遍历两个数组
  2. 比较指针所指元素,将较小的放入合并数组
  3. 当一个数组遍历完后,将另一个数组剩余元素直接加入
#include <vector>
#include <iostream>using namespace std;double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {int m = nums1.size(), n = nums2.size();vector<int> merged;int i = 0, j = 0;// 归并两个有序数组while (i < m && j < n) {if (nums1[i] <= nums2[j]) {merged.push_back(nums1[i++]);} else {merged.push_back(nums2[j++]);}}// 处理剩余元素while (i < m) merged.push_back(nums1[i++]);while (j < n) merged.push_back(nums2[j++]);// 计算中位数int total = merged.size();if (total % 2 == 1) {return merged[total / 2];} else {return (merged[total / 2 - 1] + merged[total / 2]) / 2.0;}
}

方法3:二分查找(基于中位数的作用)

构思

在统计中,中位数被用来:将一个集合划分为两个长度相等的子集,其中一个子集中的元素总是大于另一个子集中的元素。中位数只跟分割线两边的元素有关。

  • 条件 1

  • 条件 2

特殊情况

 

算法

通过比较 两个数组的长度,确保数组1的长度小于数组2,不满足则交换数组引用。

分割线要满足的条件:

nums1[i-1] \le nums2[j]

nums2[j-1] \le nums1[i]

任务转换为:在nums1的区间 [0,m] 闭区间内查找恰当的分割线。使用二分法,结束的标志是 left<right 不满足。

方法4:二分查找(基于中位数的计算)

根据中位数的定义,当 m+n 是奇数时,中位数是两个有序数组中的第 (m+n)/2 个元素,当 m+n 是偶数时,中位数是两个有序数组中的第 (m+n)/2 个元素和第 (m+n)/2+1 个元素的平均值。因此,这道题可以转化成寻找两个有序数组中的第 k 小的数,其中 k 为 (m+n)/2 或 (m+n)/2+1。

寻找第k个数:逐步排除

比较 A[k/2−1] 和 B[k/2−1] 之后,可以排除 k/2 个不可能是第 k 小的数,查找范围缩小了一半。同时,我们将在排除后的新数组上继续进行二分查找,并且根据我们排除数的个数,减少 k 的值,这是因为我们排除的数都不大于第 k 小的数

有以下三种情况需要特殊处理:

  • 如果 A[k/2−1] 或者 B[k/2−1] 越界,那么我们可以选取对应数组中的最后一个元素。在这种情况下,我们必须根据排除数的个数减少 k 的值,而不能直接将 k 减去 k/2。
  • 如果一个数组为空,说明该数组中的所有元素都被排除,我们可以直接返回另一个数组中第 k 小的元素。
  • 如果 k=1,我们只要返回两个数组首元素的最小值即可。

C

方法4

#define MIN(x, y) ((x) < (y) ? (x) : (y))int getKthElement(int* nums1, int nums1Size, int* nums2, int nums2Size, int k){int index1=0, index2=0; //排除的数的坐标范围, < index 的部分while(true){if(index1 == nums1Size){return nums2[index2 + k -1];}if(index2 == nums2Size){return nums1[index1 + k -1];}if(k == 1){return MIN(nums1[index1], nums2[index2]);}int newIndex1 = MIN(index1 + k/2 -1, nums1Size-1);int newIndex2 = MIN(index2 + k/2 -1, nums2Size-1);if(nums1[newIndex1]<=nums2[newIndex2]){k -= newIndex1 - index1 + 1;index1 = newIndex1 +1;}else{k -= newIndex2 - index2 + 1;index2 = newIndex2 +1;}}
}double findMedianSortedArrays(int* nums1, int nums1Size, int* nums2, int nums2Size) {int totalSize = nums1Size + nums2Size;if(totalSize%2 == 1){return getKthElement(nums1, nums1Size, nums2, nums2Size, (totalSize+1)/2);}else{return ( getKthElement(nums1, nums1Size, nums2, nums2Size, totalSize/2) \+ getKthElement(nums1, nums1Size, nums2, nums2Size, totalSize/2 + 1) ) / 2.0;}
}

C++

方法3

class Solution {
public:double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {if (nums1.size() > nums2.size()) {return findMedianSortedArrays(nums2, nums1);}int m = nums1.size();int n = nums2.size();int left = 0, right = m;while(left < right){int i = left + (right - left + 1) / 2;int j = (m + n + 1) / 2 - i;if (nums1[i-1] > nums2[j]) {right = i - 1;} else {left = i;}}int i = left;int j = (m + n + 1) / 2 - i;int nums1LeftMax = (i == 0 ? INT_MIN : nums1[i - 1]);int nums1RightMin = (i == m ? INT_MAX : nums1[i]);int nums2LeftMax = (j == 0 ? INT_MIN : nums2[j - 1]);int nums2RightMin = (j == n ? INT_MAX : nums2[j]);int medianLeft = max(nums1LeftMax, nums2LeftMax);int medianRight = min(nums1RightMin, nums2RightMin);return (m + n) % 2 == 0 ? (medianLeft + medianRight) / 2.0 : medianLeft;}
};

Java

方法3

class Solution {public double findMedianSortedArrays(int[] nums1, int[] nums2) {if (nums1.length > nums2.length) {return findMedianSortedArrays(nums2, nums1);}int m = nums1.length;int n = nums2.length;int left = 0, right = m;while (left < right) {int i = left + (right - left + 1) / 2;int j = (m + n + 1) / 2 - i;if (nums1[i - 1] > nums2[j]) {right = i - 1;} else {left = i;}}int i = left;int j = (m + n + 1) / 2 - i;int nums1LeftMax = (i == 0 ? Integer.MIN_VALUE : nums1[i - 1]);int nums1RightMin = (i == m ? Integer.MAX_VALUE : nums1[i]);int nums2LeftMax = (j == 0 ? Integer.MIN_VALUE : nums2[j - 1]);int nums2RightMin = (j == n ? Integer.MAX_VALUE : nums2[j]);int medianLeft = Math.max(nums1LeftMax, nums2LeftMax);int medianRight = Math.min(nums1RightMin, nums2RightMin);return (m + n) % 2 == 0 ? (medianLeft + medianRight) / 2.0 : medianLeft;}
}

Python3

方法3

import sys
from typing import Listclass Solution:def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:if len(nums1) > len(nums2):return self.findMedianSortedArrays(nums2, nums1)m, n = len(nums1), len(nums2)left, right = 0, mwhile left < right:i = left + (right - left + 1) // 2j = (m + n + 1) // 2 - iif nums1[i - 1] > nums2[j]:right = i - 1else:left = ii = leftj = (m + n + 1) // 2 - inums1_left_max = -sys.maxsize - 1 if i == 0 else nums1[i - 1]nums1_right_min = sys.maxsize if i == m else nums1[i]nums2_left_max = -sys.maxsize - 1 if j == 0 else nums2[j - 1]nums2_right_min = sys.maxsize if j == n else nums2[j]median_left = max(nums1_left_max, nums2_left_max)median_right = min(nums1_right_min, nums2_right_min)if (m + n) % 2 == 0:return (median_left + median_right) / 2.0else:return median_left

三元运算符

Java 与 C/C++ 相同,条件 ? 表达式1 : 表达式2 ;

Python:表达式1 if 条件 else 表达式2

http://www.dtcms.com/a/450002.html

相关文章:

  • 理解CC++异步IO编程:Epoll入门
  • wordpress房屋网站模板微信小程序
  • 阿里网站建设视频教程WordPress云媒体库
  • SpringCloud 入门 - Nacos 配置中心
  • Windows 下使用 Claude Code CLI 启动 Kimi
  • 网站推广的基本方式抖音特效开放平台官网
  • 湖南网站排名wordpress插件seo
  • WindowsKyLin:nginx安装与配置
  • 【剑斩OFFER】算法的暴力美学——最大连续1的个数 III
  • UNIX下C语言编程与实践32-UNIX 僵死进程:成因、危害与检测方法
  • 论坛开源网站源码首页优化排名
  • 网站建设策请seo的人帮做网站排名
  • 旅游网站后台html模板做网站的做app的
  • 网站备案回访问题效果好的网站制作
  • Unity 光源
  • 应急响应
  • 【2061】梯形面积
  • 电商网站seo优化目标分解wordpress域名授权
  • tex 写的论文如何统计字数
  • 【区块链学习笔记】16:以太坊中的交易树和收据树
  • 盟接之桥谈制造:格局、行动、心态与认知的创业修行
  • 深入理解 Spring Bean 后处理器:@Autowired 等注解的本质
  • 购物网站排名2017专业商业空间设计公司
  • UDP 的报文结构和注意事项
  • C56-字符串拷贝函数strcpy与strnpy
  • SAM、SECURITY、SYSTEM 和 NTDS.dit 的详细区别
  • 网站建网站建设企业北京网络教育
  • 通过super()正确初始化超类:Effective Python 第40条
  • 关于共享内存的梳理和总结
  • asp网站设计代做关键词推广效果分析