分治法将问题划分成多个相互独立且相同或类似的子问题,然后递归地解决每个子问题,并将结果合并以得到原始问题的解。
分治思想通常包含以下三个步骤:
核心思想
是将一个复杂的问题分解成多个简单的子问题,通过递归地解决子问题,最终将子问题的解合并成原始问题的解。
我们也遇到过一些,例如:归并排序、快速排序、二叉树的遍历等。
优缺点
优点在于能够有效地降低问题的复杂度,提高算法的效率。缺点在于需要额外的空间和时间来处理子问题的合并过程。
思路
代码
void sortColors(vector<int>& nums) {
// 三个指针划分区域
// [0, left-1] : 0
// [left, i] : 1
// [i, right-1] : 未检测
// [right, n-1] : 2
int left = -1, i = 0, right = nums.size();
while(i < right) // i,right相遇,全部检索完成
{
if(nums[i] == 0){
swap(nums[++left], nums[i++]);
}else if(nums[i] == 1){
++i;
}else{
swap(nums[--right], nums[i]);
}
}
}
思路
代码
class Solution {
public:
// 获取随机数
int getRandom(vector<int>& nums, int left, int right){
int r = rand();
return nums[r % (right - left) + left];
}
// 快排
void qsort(vector<int>& nums, int l, int r){
if(l >= r) return;
int left = l - 1, i = l, right = r + 1;
int key = getRandom(nums, l, r);
while(i < right)
{
if(nums[i] < key)
swap(nums[++left], nums[i++]);
else if(nums[i] == key)
i++;
else
swap(nums[--right], nums[i]);
}
// [l, left] [left+1, right-1] [right,r]
qsort(nums, l, left);
qsort(nums, right, r);
}
vector<int> sortArray(vector<int>& nums) {
// 快排 + 三数划分 + 随机数优化
srand(time(NULL)); // 设置随机数种子
qsort(nums, 0, nums.size() - 1);
return nums;
}
};
思路
我们知道堆排序可以用于解决topk问题,时间复杂度为O(nlogn),这里在引入快速选择算法,一样可以用于解决topK问题,时间复杂度为O(n)
代码
class Solution {
public:
// 获取随机数
int getRandom(vector<int>& nums, int left, int right){
return nums[rand() % (right - left + 1) + left];
}
// 快排
int qsort(vector<int>& nums, int l, int r, int k){
if(l == r) return nums[l];
int left = l - 1, i = l, right = r + 1;
int key = getRandom(nums, l, r);
while(i < right)
{
if(nums[i] < key) swap(nums[++left], nums[i++]);
else if(nums[i] > key) swap(nums[--right], nums[i]);
else i++;
}
// 找第k大的数
// [l, left] [left + 1, right - 1] [right, r]
// 区间元素个数: a b c
// int a = left - l + 1;
int b = (right - 1) - (left + 1) + 1, c = r - right + 1;
if(c >= k) return qsort(nums, right, r, k); // 右区间 第k位
else if(b + c >= k) return key;
// else if(a >= k) qsort(nums, l, left, k-b-c);
else return qsort(nums, l, left, k-b-c);
}
int findKthLargest(vector<int>& nums, int k) {
// 快速选择算法
srand(time(NULL));
return qsort(nums, 0, nums.size() - 1, k);
}
};
思路
代码
class Solution {
public:
int getRandom(vector<int> &arr, int left, int right){
return arr[rand() % (right - left + 1) + left];
}
void qsort(vector<int>& arr, int l, int r, int k){
if(l >= r) return;
int left = l - 1, i = l , right = r + 1;
int key = getRandom(arr, l, r);
while(i < right){
if(arr[i] < key) swap(arr[++left], arr[i++]);
else if(arr[i] > key) swap(arr[--right], arr[i]);
else i++;
}
// [l, left] [left+1, right-1] [right, r]
// 左区间长度: a | 中间区间长度: b
int a = left - l + 1, b = right - left - 1;
if(a > k) qsort(arr, l, left, k);
else if(a + b >= k) return;
else qsort(arr, right, r, k - a - b);
}
vector<int> smallestK(vector<int>& arr, int k) {
// 三数划分 + 随机数作key + 快速选择
srand(time(NULL)); // 设置随机数种子
qsort(arr, 0, arr.size()-1, k);
return vector<int>(arr.begin(), arr.begin() + k);
}
};
思路
代码
class Solution {
public:
vector<int> tmp; // 临时数组 当递归创建多次时,全局遍历节省时间开销
void mergeSort(vector<int>& nums, int left, int right){
if(left >= right) return;
int mid = left + (right - left) / 2;
// int mid = (left + right) >> 1;
// 1. 数组划分
mergeSort(nums, left, mid);
mergeSort(nums, mid + 1, right);
// 2. 合并数组
int cur1 = left, cur2 = mid + 1, i = 0;
while(cur1 <= mid && cur2 <= right)
tmp[i++] = nums[cur1] <= nums[cur2] ? nums[cur1++] : nums[cur2++];
// 2.5 处理未遍历完的数
while(cur1 <= mid) tmp[i++] = nums[cur1++];
while(cur2 <= right) tmp[i++] = nums[cur2++];
// 3. 将元素写入到nums中
for(int i = left; i <= right; ++i)
nums[i] = tmp[i - left];
}
vector<int> sortArray(vector<int>& nums) {
// 归并排序
tmp.resize(nums.size());
mergeSort(nums, 0, nums.size() - 1);
return nums;
}
};
思路
解法一:暴力枚举
解法二:归并
归并
代码
class Solution {
public:
vector<int> tmp;
// 归并排序 + 计算逆序对个数
int mergeSort(vector<int>& nums, int left, int right){
if(left >= right) return 0;
// 划分数组
int mid = left + (right - left) / 2;
int ret = 0; // 结果
ret += mergeSort(nums, left, mid);
ret += mergeSort(nums, mid + 1, right);
// 左边部分 排序 右边部分 排序
int cur1 = left, cur2 = mid + 1, i = 0;
while(cur1 <= mid && cur2 <= right){
if(nums[cur1] <= nums[cur2])
tmp[i++] = nums[cur1++]; // 排序
else{
ret += mid - cur1 + 1; // 更新结果 + 排序
tmp[i++] = nums[cur2++];
}
}
// 将未合并元素加上
while(cur1 <= mid) tmp[i++] = nums[cur1++];
while(cur2 <= right) tmp[i++] = nums[cur2++];
// 还原数组
for(int i = left; i <= right; ++i)
nums[i] = tmp[i - left];
return ret;
}
int reversePairs(vector<int>& record) {
tmp.resize(record.size());
return mergeSort(record, 0, record.size()-1);
}
};
思路
题意分析:题目要求找到数组nums中 “每一位其右侧小于该位的个数”,且将该个数存放到新数组count中
解法一:暴力枚举
解法二:归并
代码
class Solution {
public:
vector<int> ret; // 结果数组
vector<int> index; // 记录元素移动前原始下标
int tmpIndex[100001]; // 临时存放下标
int tmpNums[100001]; // 临时存放元素
void mergeSort(vector<int>& nums, int left, int right){
if(left >= right) return;
// 根据中点划分数组
int mid = left + (right-left) / 2;
// 递归排序+找数
mergeSort(nums, left, mid);
mergeSort(nums, mid + 1, right);
// 具体排序找数过程
int cur1 = left, cur2 = mid + 1, i = 0;
while(cur1 <= mid && cur2 <= right){
if(nums[cur1] <= nums[cur2]){
tmpNums[i] = nums[cur2];
tmpIndex[i++] = index[cur2++];
}else{
ret[index[cur1]] += right - cur2 + 1;
tmpNums[i] = nums[cur1];
tmpIndex[i++] = index[cur1++];
}
}
// 排序剩余元素
while(cur1 <= mid){
tmpNums[i] = nums[cur1];
tmpIndex[i++] = index[cur1++];
}
while(cur2 <= right){
tmpNums[i] = nums[cur2];
tmpIndex[i++] = index[cur2++];
}
// 还原数组
for(int j = left; j <= right; ++j){
nums[j] = tmpNums[j-left]; // tmpNums是从0开始的,所以这里还原从0-left
index[j] = tmpIndex[j-left];
}
}
vector<int> countSmaller(vector<int>& nums) {
// 哈希思想
int n = nums.size();
ret.resize(n);
index.resize(n);
// 初始化index数组 保存原始下标
for(int i = 0; i < n-1; ++i)
index[i] = i;
mergeSort(nums, 0, n-1);
return ret;
}
};
思路
解法一:暴力枚举
解法二:归并
代码
class Solution {
public:
vector<int> tmp;
int mergeSort(vector<int>& nums, int left, int right) {
if (left >= right) return 0;
int ret = 0;
// 先计算左右两侧的翻转对
int mid = left + (right - left) / 2;
ret += mergeSort(nums, left, mid);
ret += mergeSort(nums, mid + 1, right);
// 计算翻转对操作
int cur1 = left, cur2 = mid + 1;
while(cur1 <= mid)
{
while(cur2 <= right && (nums[cur1] / 2.0 <= nums[cur2]))
cur2++; // 找到符合条件的cur2位置,用*可能溢出
if(cur2 > right) break;
ret += right - cur2 + 1;
++cur1;
}
// 具体排序
// 降序判断
cur1 = left, cur2 = mid + 1;
int i = 0;
while(cur1 <= mid && cur2 <= right)
tmp[i++] = nums[cur1] <= nums[cur2] ? nums[cur2++] : nums[cur1++];
// 排序剩余元素
while(cur1 <= mid) tmp[i++] = nums[cur1++];
while(cur2 <= right) tmp[i++] = nums[cur2++];
// 还原数组
for(int j = left; j <= right; ++j)
nums[j] = tmp[j - left];
return ret;
}
int reversePairs(vector<int>& nums) {
tmp.resize(nums.size());
return mergeSort(nums, 0, nums.size() - 1);
}
};