算法
分治算法
拆分为独立子问题,递归求解,合并结果——归并排序是最典型的范例
分治框架
分治三步:Divide(将问题拆成独立子问题)→ Conquer(递归求解)→ Combine(合并子问题结果)。关键性质是子问题相互独立(与 DP 不同,DP 有重叠子问题)。
主定理(Master Theorem)
T(n) = aT(n/b) + f(n),其中 a 个子问题,规模 n/b,合并代价 f(n)。三种情形:若 f(n) = O(n^log_b(a) / log^k n),则 T(n) = Θ(n^log_b(a) * log^(k+1) n)(均摊情形);工程中最常遇到的是 a=2,b=2,f(n)=O(n) → T(n)=O(n log n)(归并排序)。
常见算法复杂度
算法T(n) 递推结果
归并排序2T(n/2) + O(n)O(n log n)
Quick Select(期望)T(n/2) + O(n)O(n)
二分查找T(n/2) + O(1)O(log n)
矩阵快速幂T(log n) * O(k³)O(k³ log n)
适用场景
- 问题可拆成规模相近的独立子问题
- 合并步骤是整个算法的核心逻辑
- 需要稳定排序(归并)或精确中位数(Quick Select 随机化)
归并排序 & 逆序对
归并排序:拆成左右两半,递归排序,然后双指针合并两个有序子数组。稳定,O(n log n) 最坏,但需要 O(n) 额外空间。逆序对计数可在合并步骤中 O(1) 额外计算:右半的元素比左半当前元素小时,左半剩余元素个数即逆序对数。
// Merge Sort + count inversions
long long mergeCount(vector<int>& a, int l, int r) {
if (r - l <= 1) return 0;
int mid = (l + r) / 2;
long long cnt = mergeCount(a, l, mid) + mergeCount(a, mid, r);
vector<int> tmp;
int i = l, j = mid;
while (i < mid && j < r) {
if (a[i] <= a[j]) tmp.push_back(a[i++]);
else { cnt += mid - i; tmp.push_back(a[j++]); } // all a[i..mid) > a[j]
}
while (i < mid) tmp.push_back(a[i++]);
while (j < r) tmp.push_back(a[j++]);
copy(tmp.begin(), tmp.end(), a.begin() + l);
return cnt;
}典型题目
- 912. 排序数组(归并实现)
- 315. 计算右侧小于当前元素的个数(逆序对变体)
- 493. 翻转对(自定义逆序对条件:nums[i] > 2*nums[j])
- 23. 合并 K 个升序链表(K 路归并,见堆章节)
稳定性:归并排序是稳定排序(相等元素保持原有相对顺序),快速排序默认不稳定。若需稳定且 O(n log n),选归并。
练习:分治 — 归并排序 — 代码练习
测试用例(3 个)
用例 1
输入:
5
3 1 4 1 5期望:
1 1 3 4 5
用例 2
输入:
4
4 3 2 1期望:
1 2 3 4
用例 3
输入:
1
7期望:
7
练习:分治 — 逆序对计数 — 代码练习
测试用例(3 个)
用例 1
输入:
5
2 4 1 3 5期望:
3
用例 2
输入:
3
3 2 1期望:
3
用例 3
输入:
4
1 2 3 4期望:
0
Quick Select
Quick Select 找第 k 小(大)元素。随机选 pivot,划分后只递归 pivot 所在的一侧,期望 O(n),最坏 O(n²)(随机 pivot 可几乎消除最坏情形)。
算法步骤
- 随机选 pivot,将数组划分为 [< pivot][pivot][> pivot]
- 若 pivot 下标 == k,直接返回
- 否则只递归 k 所在的一侧(不需要两侧都处理)
// Quick Select — kth smallest (0-indexed)
int quickSelect(vector<int>& nums, int l, int r, int k) {
if (l == r) return nums[l];
int pivot = nums[l + rand() % (r - l + 1)];
int i = l, j = r;
while (i <= j) {
while (nums[i] < pivot) i++;
while (nums[j] > pivot) j--;
if (i <= j) swap(nums[i++], nums[j--]);
}
// after partition: nums[l..j] <= pivot <= nums[i..r]
if (k <= j) return quickSelect(nums, l, j, k);
if (k >= i) return quickSelect(nums, i, r, k);
return pivot;
}典型题目
- 215. 数组中的第 K 个最大元素(Quick Select 或堆)
- 347. 前 K 个高频元素(统计频次后 Quick Select)
vs 堆:Quick Select 期望 O(n),适合只需一个 Kth 值且不需要前 K 个都有序的情形;堆 O(n log k) 且前 K 个可按顺序弹出。
练习:分治 — Quick Select 第 K 大 — 代码练习
测试用例(3 个)
用例 1
输入:
6 2
3 2 1 5 6 4期望:
5
用例 2
输入:
4 4
1 2 3 4期望:
1
用例 3
输入:
1 1
7期望:
7