1. 排序:912.排序数组
给你一个整数数组 nums,请你将该数组升序排列。
你必须在 不使用任何内置函数 的情况下解决问题,时间复杂度为 O(nlog(n)),并且空间复杂度尽可能小。

1.1 插入排序
时间复杂度:O(n方)
数组前面是已排序部分,后面是未排序部分。
思路:从未排序部分开始遍历并插入到已排序部分。
选择数组后面未排序的元素插入到数组前面已排序的部分,从第二个元素开始选择。
cpp
class Solution {
public:
vector<int> sortArray(vector<int>& nums) {
int n = nums.size();
// 插入排序,遍历未排序部分插入到前面已排序部分,初始时第一个元素看作已排序
for(int unsortedBegin = 1; unsortedBegin < n; unsortedBegin++){
int cur = nums[unsortedBegin];
int j = unsortedBegin - 1; // 已排序部分末尾
while(j >= 0 && nums[j] > cur){
nums[j + 1] = nums[j];
j--;
}
nums[j + 1] = cur;
}
return nums;
}
};
1.2 冒泡排序
时间复杂度:O(n方)
数组前面是未排序部分,后面是已排序部分。
思路:从未排序部分选择最大的放到已排序部分前面一个位置。
双重循环,第一重循环共 n - 1 次因为每次循环可以排序一个元素,共需要排序 n - 1 个元素,第二重循环两两比较,选择更大的元素不断交换到数组后面成为已排序的一部分。
cpp
class Solution {
public:
vector<int> sortArray(vector<int>& nums) {
int n = nums.size();
for(int i = 0; i < n - 1; i++){
for(int j = 0; j < n - 1; j++){
if(nums[j] > nums[j + 1]) swap(nums[j], nums[j + 1]);
}
}
return nums;
}
};
cpp
class Solution {
public:
vector<int> sortArray(vector<int>& nums) {
int n = nums.size();
int sortedBegin = n;
// 冒泡排序,不断选择未排序部分中最大的放到已排序部分前面一个位置
for(int i = 0; i <= n - 2; i++){
for(int j = 0; j <= sortedBegin - 2; j++){
if(nums[j] > nums[j + 1]) swap(nums[j], nums[j + 1]);
}
// 内层循环结束,最大的元素被交换到了 sortedBegin - 1 的位置
sortedBegin--;
}
return nums;
}
};
1.3 选择排序
时间复杂度:O(n方)
数组前面是已排序的,后面是未排序的。
思路:从未排序部分选择最小的,放到已排序部分的末尾后一个位置。
cpp
class Solution {
public:
vector<int> sortArray(vector<int>& nums) {
int n = nums.size();
int sortedBegin = n;
// 选择排序,不断从未排序部分选择最小的插入到已排序部分末尾后面一个位置
for(int unsortedBegin = 0; unsortedBegin < n; unsortedBegin++){
int minIdx = unsortedBegin;
for(int j = unsortedBegin; j < n; j++){
if(nums[j] < nums[minIdx]) minIdx = j;
}
// 遍历结束,找到最小元素 minIdx
// 将其放到未排序部分后面一个位置,即已排序部分开头
swap(nums[minIdx], nums[unsortedBegin]);
}
return nums;
}
};
1.4 快速排序
- 分解 :选择一个基准值,将数组分成两部分:左边都比它小,右边都比它大。
- 解决:递归地对左右两部分重复上述过程。
时间复杂度:O(nlogn)
思路:遍历后面的未选择区域,把比pivot小的放在前面已选择区域末尾后一个位置。
cpp
class Solution {
public:
vector<int> sortArray(vector<int>& nums) {
int n = nums.size();
auto partition = [&](int left, int right) -> int {
// 选择中点
int pivotIdx = left + (right - left) / 2;
int pivot = nums[pivotIdx];
// 将基准值放最后方便后面换回来
swap(nums[pivotIdx], nums[right]);
// 小于pivot区域的末尾
int end = left - 1;
// 把小于pivot的放在前面
for(int i = left; i < right; i++){
if(nums[i] < pivot){
end++;
swap(nums[i], nums[end]);
}
}
// 把 pivot 放到正确的位置
swap(nums[right], nums[end + 1]);
// 返回pivot的位置
return end + 1;
};
auto quickSort = [&](this auto&& quickSort, int left, int right) -> void {
if(left < right){
int pivotIdx = partition(left, right);
quickSort(left, pivotIdx - 1);
quickSort(pivotIdx + 1, right);
}
};
quickSort(0, n - 1);
return nums;
}
};
1.5 归并排序
- 分解:将数组从中间切开,递归地对左右两半进行排序。
- 合并:将两个已经有序的子数组合并成一个大的有序数组。
时间复杂度:O(nlogn)
cpp
class Solution {
public:
vector<int> sortArray(vector<int>& nums) {
int n = nums.size();
// 归并排序,递归,将mid左右两边的通过一个临时数组合并在一起
// 合并[left, mid]和[mid + 1, right]
auto merge = [&](int left, int mid, int right) -> void {
vector<int> tmp; // 临时数组
int i = left, j = mid + 1; // 双指针遍历两个数组
// 1. 把两个数组中较小的那一个放进去
while(i <= mid && j <= right){
if(nums[i] < nums[j]){
tmp.push_back(nums[i]);
i++;
}else{
tmp.push_back(nums[j]);
j++;
}
}
// 2. 把剩下的都放进去
while(i <= mid){
tmp.push_back(nums[i]);
i++;
}
while(j <= right){
tmp.push_back(nums[j]);
j++;
}
// 3. tmp中现在是排序好的,拷贝到原数组中
for(int k = 0; k < tmp.size(); k++){
nums[left + k] = tmp[k];
}
};
auto mergeSort = [&](this auto&& mergeSort ,int left, int right) -> void {
if(left < right){
int mid = left + (right - left) / 2;
// 对左右两边进行排序
mergeSort(left, mid);
mergeSort(mid + 1, right);
// 左右两边排序完成了,现在将其合并
merge(left, mid, right);
}
};
mergeSort(0, n - 1);
return nums;
}
};
1.6 堆排序
1.6.1 大根堆
大根堆 是一种特殊的**完全二叉树,**它满足两个核心条件:
- 结构性质 :必须是一棵完全二叉树(除了最后一层,其他层都填满了,且最后一层的节点都靠左排列)。
- 堆序性质 :树中任意一个节点的值,都大于或等于其左右孩子节点的值 。这意味着堆顶(根节点)一定是整个堆中最大的元素。
- 逻辑视角: 它是一棵树 。
- 有根节点、左孩子、右孩子。
- 有层级关系。
- 你可以画成一个三角形的树状图。
- 物理视角: 它通常是一个数组 。
- 因为它是"完全二叉树",中间没有空缺,所以非常适合用数组紧凑存储,不浪费空间。
- 通过下标计算来模拟父子关系,不需要像链表那样存指针。缓存友好:数组在内存中是连续的,CPU 缓存命中率高,速度更快。
| 关系 | 公式 | 说明 |
|---|---|---|
| 左孩子 | 2 * i + 1 |
找到左边孩子的下标 |
| 右孩子 | 2 * i + 2 |
找到右边孩子的下标 |
| 父节点 | (i - 1) / 2 |
找到父节点的下标 (向下取整) |
大根堆不是静态的,它支持动态操作,操作后会通过"调整"来维持大根堆的性质:
- 插入(Push) :
- 先把新元素放到数组末尾。
- 然后让它向上浮(Sift Up):如果它比父节点大,就和父节点交换,直到满足大根堆性质。
- 删除堆顶(Pop) :
- 通常只能删除最大值(即根节点)。
- 把数组最后一个元素移到根节点覆盖掉原来的根。
- 然后让它向下沉(Sift Down):如果它比孩子小,就和较大的那个孩子交换,直到满足大根堆性质。
1.6.2 算法
时间复杂度:O(nlogn)
- 把最大的数拿到最左边(建堆)
- 把最左边的数放到末尾(swap)
- 忽略末尾的数,重复前两步
在数组存储的完全二叉树中,父子索引关系是固定的:
- 父节点索引:i
- 左孩子索引:2 * i + 1
- 右孩子索引:2 * i + 2
- 索引
n / 2 - 1一定是最后一个拥有孩子的节点。任何大于这个索引的节点(即n/2到n-1),都一定是叶子节点。
| 特性 | std::priority_queue(优先队列) | 堆排序 (Heap Sort) |
|---|---|---|
| 目的 | 随时获取当前最大值,并支持动态插入/删除。 | 把整个数组彻底排好序。 |
| 建堆 | 只做一次:for (n/2-1 -> 0) heapify。 | 只做一次:for (n/2-1 -> 0) heapify。 |
| 后续操作 | 动态维护 : 每次 push 新元素,执行一次"上浮"; 每次 pop 顶元素,执行一次"下沉"。 (不一次性把所有数排好) | 循环提取 : 循环 n 次: 1. 交换堆顶和末尾 2. 缩小范围 3. 执行一次 heapify (直到数组完全有序) |
| 最终结果 | 数组内部依然是堆结构(乱序的,只是满足堆性质)。 | 数组变成了完全有序(升序)。 |
cpp
class Solution {
public:
vector<int> sortArray(vector<int>& nums) {
int n = nums.size();
auto heapify = [&](this auto&& heapify, int end, int root) -> void {
int largest = root; // 假设root为当前最大值
int left = 2 * root + 1; // 左孩子
int right = 2 * root + 2; // 右孩子
if(left < end && nums[left] > nums[largest]) largest = left;
if(right < end && nums[right] > nums[largest]) largest = right;
// 下沉
if(largest != root){
swap(nums[largest], nums[root]);
heapify(end, largest);
}
};
// 1. 把数组转化为大根堆
for(int i = n/2 - 1; i >= 0; i--){
heapify(n, i);
}
// 2. 循环排序,依次将堆顶元素(最大值)与末尾交换,然后缩小堆范围再调整
for(int end = n - 1; end >= 0; end--){
swap(nums[0], nums[end]);
heapify(end, 0);
}
return nums;
}
};
2. 快速选择
时间复杂度:O(n)
1. 快速排序的逻辑:
- 目标:把整个数组排好序。
- 终止条件 :当区间长度为 1 (
left == right) 时,它已经是有序的了,不需要再划分。 - 所以 :用if (left < right) 。如果是left == right,直接返回,不做任何事。
2. 快速选择的逻辑:
- 目标:找到第 k 大的元素(即下标为 targetIdx 的元素)。
- 区别 :不关心整个数组是否有序,只关心
targetIdx位置上的值。 - 场景 : 假设数组缩小到了只剩一个元素,且这个元素的下标正好就是要找的
targetIdx。- 此时 left == right == targetIdx。
- 如果用
<:- 条件
left < right(即targetIdx < targetIdx) 为 假。 - 函数直接返回,导致答案丢失。
- 条件
- 如果用
<=:- 条件
left <= right为 真。 - 进入函数,执行
partition。 - 因为只有一个元素,
partition返回的下标pivotIdx必然等于left(也就是targetIdx)。 - 进入判断 if (pivotIdx == targetIdx),成功找到并记录答案。
- 条件
2.1 215.数组中的第K个最大元素
给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。
请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
你必须设计并实现时间复杂度为 O(n) 的算法解决此问题。

类快速排序的标准递归写法:
cpp
class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
int n = nums.size();
// 第 k 大的元素在排序数组中的小标是 n - k
int targetIdx = n - k;
auto partition = [&](int left, int right) -> int {
// 随机选择基准点
int pivotIdx = left + (right - left) / 2;
int pivot = nums[pivotIdx];
// 把基准值放最后方便后续操作
swap(nums[right], nums[pivotIdx]);
// 已选择部分的末尾
int end = left - 1;
// 把比pivot小的放前面
for(int i = left; i < right; i++){
if(nums[i] < pivot){
end++;
swap(nums[end], nums[i]);
}
}
// 把基准值放回去
swap(nums[right], nums[end + 1]);
return end + 1;
};
int ans = 0;
auto quickSort = [&](this auto&& quickSort, int left, int right) -> void {
if(ans != 0) return;
if(left <= right){
int pivotIdx = partition(left, right);
if(pivotIdx == targetIdx){
ans = nums[pivotIdx];
}else if(pivotIdx > targetIdx){
quickSort(left, pivotIdx - 1);
}else{
quickSort(pivotIdx + 1, right);
}
}
};
quickSort(0, n - 1);
return ans;
}
};
迭代写法:
cpp
class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
int n = nums.size();
// 第 k 大的元素在排序数组中的小标是 n - k
int targetIdx = n - k;
auto partition = [&](int left, int right) -> int {
// 随机选择基准点
int pivotIdx = left + rand() % (right - left + 1);
int pivot = nums[pivotIdx];
// 把基准值放最后方便后续操作
swap(nums[right], nums[pivotIdx]);
// 已选择部分的末尾
int end = left - 1;
// 把比pivot小的放前面
for(int i = left; i < right; i++){
if(nums[i] < pivot){
end++;
swap(nums[end], nums[i]);
}
}
// 把基准值放回去
swap(nums[right], nums[end + 1]);
return end + 1;
};
int left = 0, right = n - 1;
srand(time(NULL));
while(left <= right){
int pivotIdx = partition(left, right);
if(pivotIdx == targetIdx){
return nums[pivotIdx];
}else if(pivotIdx > targetIdx){
right = pivotIdx - 1;
}else{
left = pivotIdx + 1;
}
}
return -1;
}
};
2.2 347. 前 K 个高频元素
给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。

思路:统计频率后对频率进行快速排序。
类快速排序的标准递归写法:
cpp
class Solution {
public:
vector<int> topKFrequent(vector<int>& nums, int k) {
// 1. 统计频率
unordered_map<int, int> m;
for(int i = 0; i < nums.size(); i++){
m[nums[i]]++;
}
// 2. 转化为数组
vector<pair<int, int>> vec;
for(auto& p : m){
vec.push_back(p);
}
auto partition = [&](int left, int right) -> int {
int pivotIdx = left + rand() % (right - left + 1);
int pivot = vec[pivotIdx].second;
swap(vec[right], vec[pivotIdx]);
int end = left - 1;
for(int i = left; i < right; i++){
if(vec[i].second < pivot){
end++;
swap(vec[end], vec[i]);
}
}
swap(vec[end + 1], vec[right]);
return end + 1;
};
// 3. 找到前 k 大的,需要在升序中找到第 n - k 个元素,以及后面的所有元素
int n = vec.size();
int targetIdx = n - k;
int flag = 0;
auto quickSort = [&](this auto&& quickSort, int left, int right) -> void {
if(flag == 1) return;
if(left <= right){
int pivotIdx = partition(left, right);
if(pivotIdx == targetIdx){
flag = 1;
return;
}else if(pivotIdx > targetIdx){
quickSort(left, pivotIdx - 1);
}else{
quickSort(pivotIdx + 1, right);
}
}
};
quickSort(0, n - 1);
// 找到 targetIdx 即可,不需要排序,因为在选择的过程中已经确保右边的都是大于 partition 的
// 且没有要求返回答案的顺序
// 4. 提取后 k 个元素
vector<int> ans;
for(int i = n - 1; i >= targetIdx; i--){
ans.push_back(vec[i].first);
}
return ans;
}
};
迭代写法:
cpp
class Solution {
public:
vector<int> topKFrequent(vector<int>& nums, int k) {
// 1. 统计频率
unordered_map<int, int> m;
for(int i = 0; i < nums.size(); i++){
m[nums[i]]++;
}
// 2. 转化为数组
vector<pair<int, int>> vec;
for(auto& p : m){
vec.push_back(p);
}
auto partition = [&](int left, int right) -> int {
int pivotIdx = left + rand() % (right - left + 1);
int pivot = vec[pivotIdx].second;
swap(vec[right], vec[pivotIdx]);
int end = left - 1;
for(int i = left; i < right; i++){
if(vec[i].second < pivot){
end++;
swap(vec[end], vec[i]);
}
}
swap(vec[end + 1], vec[right]);
return end + 1;
};
// 3. 找到前 k 大的,需要在升序中找到第 n - k 个元素,以及后面的所有元素
int n = vec.size();
int targetIdx = n - k;
int left = 0, right = n - 1;
while(left <= right){
int pivotIdx = partition(left, right);
if(pivotIdx == targetIdx){
break;
}else if(pivotIdx > targetIdx){
right = pivotIdx - 1;
}else{
left = pivotIdx + 1;
}
}
// 找到 targetIdx 即可,不需要排序,因为在选择的过程中已经确保右边的都是大于 partition 的
// 且没有要求返回答案的顺序
// 4. 提取后 k 个元素
vector<int> ans;
for(int i = n - 1; i >= targetIdx; i--){
ans.push_back(vec[i].first);
}
return ans;
}
};
3. 二分查找
二分查找的关键在于查找的是存在的值还是不存在的值。
如果确定该值一定存在数组中,则:
cpp
int n = nums.size();
int l = 0, r = n - 1;
while(l < r){
int mid = l + (r - l) / 2;
if(条件){
}else{
}
}
return l;
因为 l 的范围是 [0, n - 1],最后返回的 l 一定是要查找的值。
如果不确定该值是否存在,则:
cpp
int n = nums.size();
int l = 0, r = n;
while(l < r){
int mid = l + (r - l) / 2;
if(条件){
}else{
}
}
return l;
因为 l 的范围是 [0, n] ,该值需要放插入到的位置可能在数组末尾。
3.1 35.搜索插入位置
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
请必须使用时间复杂度为 O(log n) 的算法。

关键:循环结束时,l = r,l 永远指向"第一个 >= target"的位置
cpp
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int l = 0, r = nums.size();
while(l < r){
int mid = l + (r - l) / 2;
if(nums[mid] < target){
l = mid + 1;
}else{
r = mid;
}
}
return l;
}
};
3.2 74.搜索二维矩阵
给你一个满足下述两条属性的 m x n 整数矩阵:
- 每行中的整数从左到右按非严格递增顺序排列。
- 每行的第一个整数大于前一行的最后一个整数。
给你一个整数 target ,如果 target 在矩阵中,返回 true ;否则,返回 false 。

将该矩阵展平即为一个升序的数组,可使用双指针进行二分查找。
cpp
class Solution {
public:
bool searchMatrix(vector<vector<int>>& matrix, int target) {
int row = matrix.size();
int col = matrix[0].size();
int l = 0, r = row * col;
while(l < r){
int mid = l + (r - l) / 2;
int num = matrix[mid / col][mid % col];
if(num < target){
l = mid + 1;
}else{
r = mid;
}
}
if(l <= row * col - 1 && matrix[l / col][l % col] == target)
return 1;
return 0;
}
};
3.3 34.在排序数组中查找元素的第一个和最后一个位置
给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target,返回 [-1, -1]。
你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。

使用二分法查找起始位置,再用二分法查找 target + 1的起始位置。
cpp
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
int n = nums.size();
int l = 0, r = n;
while(l < r){
int mid = l + (r - l) / 2;
if(nums[mid] < target){
l = mid + 1;
}else{
r = mid;
}
}
if(l == n || nums[l] != target) return {-1, -1};
int start = l;
l = 0, r = n;
while(l < r){
int mid = l + (r - l) / 2;
if(nums[mid] < target + 1){
l = mid + 1;
}else{
r = mid;
}
}
return {start, l - 1};
}
};
3.4 153.寻找旋转排序数组中的最小值
已知一个长度为 n 的数组,预先按照升序排列,经由 1 到 n 次 旋转 后,得到输入数组。例如,原数组 nums = [0,1,2,4,5,6,7] 在变化后可能得到:
- 若旋转
4次,则可以得到[4,5,6,7,0,1,2] - 若旋转
7次,则可以得到[0,1,2,4,5,6,7]
注意,数组 [a[0], a[1], a[2], ..., a[n-1]] 旋转一次 的结果为数组 [a[n-1], a[0], a[1], a[2], ..., a[n-2]] 。
给你一个元素值 互不相同 的数组 nums ,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的 最小元素 。
你必须设计一个时间复杂度为 O(log n) 的算法解决此问题。

一旦数组"断开"了(旋转了),看起来不连续了,直觉上会觉得二分查找失效。但实际上,二分查找的核心本质不是"数组必须全局有序",而是"每次比较后,能够确定目标值在哪一半,从而排除掉另一半" 。只要能找到一种方法,每次都能排除掉一半不可能存在答案的区间,二分查找就可以使用。
二分查找不需要全局单调,只需要满足:对于当前的 mid,能判断出答案在左边还是右边。
- 在普通有序数组中:
nums[mid] < target,因为全局递增,所以答案一定在右边。 - 在旋转数组找最小值中:
nums[mid] < nums[high],因为右半部分局部有序 ,所以最小值一定不在(mid, high]之间,答案一定在左边(包含 mid)。
只要能根据 mid 的信息,扔掉一半的数据,这就是二分查找。 旋转数组的特殊结构(两段有序)恰好提供了这个扔掉一半数据的依据。
cpp
class Solution {
public:
int findMin(vector<int>& nums) {
int n = nums.size();
int l = 0, r = n - 1;
while(l < r){
int mid = l + (r - l) / 2;
if(nums[mid] < nums[n - 1]){
r = mid;
}else{
l = mid + 1;
}
}
return nums[l];
}
};
findMin(寻找旋转数组最小值) :- 最小值一定在数组里。
- 答案
l的下标范围是[0, n-1]。 - 所以
r = n - 1是安全的,足够覆盖所有可能。
searchInsert(寻找插入位置) :- 插入位置可能在数组末尾之后。
- 答案
l的下标范围是[0, n](共n+1种可能)。 - 如果
target比数组所有元素都大,应该返回n。 - 如果写
r = n - 1,那么l最大只能变成n - 1,永远无法返回n。
3.5 33.搜索旋转排序数组
整数数组 nums 按升序排列,数组中的值 互不相同 。
在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums.length)上进行了 向左旋转 ,使数组变为 [nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2,4,5,6,7] 下标 3 上向左旋转后可能变为 [4,5,6,7,0,1,2] 。
给你 旋转后 的数组 nums 和一个整数 target ,如果 nums 中存在这个目标值 target ,则返回它的下标,否则返回 -1 。
你必须设计一个时间复杂度为 O(log n) 的算法解决此问题。

cpp
class Solution {
public:
int search(vector<int>& nums, int target) {
// 1. 寻找旋转排序数组中的最小值
int n = nums.size();
int l = 0, r = n - 1;
while(l < r){
int mid = l + (r - l) / 2;
if(nums[mid] < nums[n - 1]){
r = mid;
}else{
l = mid + 1;
}
}
int minIdx = l;
// 2. 二分查找
if(target > nums[n - 1]){
// 在左边搜
l = 0, r = minIdx;
while(l < r){
int mid = l + (r - l) / 2;
if(nums[mid] < target){
l = mid + 1;
}else{
r = mid;
}
}
}else{
// 在右边搜
l = minIdx, r = n;
while(l < r){
int mid = l + (r - l) / 2;
if(nums[mid] < target){
l = mid + 1;
}else{
r = mid;
}
}
}
if(nums[l] == target) return l;
return -1;
}
};
3.6 4.寻找两个正序数组的中位数
给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。
算法的时间复杂度应该为 O(log (m+n)) 。

有两个有序数组 nums1 和 nums2。 如果我们将这两个数组分别切一刀(分成左右两部分),使得:
- 左半部分的元素个数 = 右半部分的元素个数(如果总长度是奇数,左边多一个)。
- 左半部分的所有元素 ≤ 右半部分的所有元素。
那么,中位数就只取决于分割线附近的四个数字:
nums1左边的最大值 (num1_left)nums1右边的最小值 (num1_right)nums2左边的最大值 (num2_left)nums2右边的最小值 (num2_right)
中位数的计算:
- 总长度为奇数 :中位数就是左边最大的那个数,即
max(num1_left,num2_left)。 - 总长度为偶数 :中位数是左边最大和右边最小的平均值,即
(max(num1_left,num2_left) + min(num1_right,num2_right)) / 2.0。
cpp
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
// 用较小的数组进行二分
if(nums1.size() > nums2.size()) return findMedianSortedArrays(nums2, nums1);
int n1 = nums1.size(), n2 = nums2.size();
// 闭区间[0, n1],因为循环内的是切割点,切割点可能在数组最后, 切割点即 nums1 左边元素的个数
int left = 0, right = n1;
int mid1 = 0, mid2 = 0;
// 用 = 是因为要覆盖并检查所有可能的切割点
while(left <= right){
int idx1 = left + (right - left) / 2; // 第一个数组切割点
int idx2 = (n1 + n2 + 1) / 2 - idx1; // 第二个数组切割点,保证左边与右边相等或者多一个
// 切割后数组两部分长度相同,只要判断大小是否满足即可
int num1_left = idx1 == 0 ? INT_MIN : nums1[idx1 - 1];
int num1_right = idx1 == n1 ? INT_MAX : nums1[idx1];
int num2_left = idx2 == 0 ? INT_MIN : nums2[idx2 - 1];
int num2_right = idx2 == n2 ? INT_MAX : nums2[idx2];
// 如果大小匹配则为中位数
if(num1_left <= num2_right && num2_left <= num1_right){
mid1 = max(num1_left, num2_left);
mid2 = min(num1_right, num2_right);
break;
}else if(num1_left > num2_right){
// 分割点向左
right = idx1 - 1;
}else{
left = idx1 + 1;
}
}
return (n1 + n2) % 2 == 0 ? (mid1 + mid2) / 2.0 : mid1;
}
};