归并排序
1.归并排序是分治思想, 对于一个长度为 n 的序列, 分解成左边和右边两个长度为 n/2 的序列, 每次递归调用函数使得左右两个子序列有序, 然后将两个序列合并使得整个序列有序. 归并思路框架如下
void mergeSort(vector<int>& nums, int low, int high) {
// 处理单个元素的场景
if (low == high)
return;
int mid = low + (r - l) / 2;
// 递归排序左侧nums[low, mid]
mergeSort(nums, low, mid);
// 递归排序右侧nums[mid+1, high]
mergeSort(nums, mid + 1, high);
// 合并有序数组nums[l, mid]和有序数组nums[mid+1, high], 利用双指针扫描
merge(nums, low, mid, high);
}
所有的递归算法, 本质上是在遍历一棵树, 然后在节点上执行逻辑, 归并排序和二叉树的后序遍历是类似的
void traverse(TreeNode* root) {
if (root == nullptr) return;
traverse(root->left);
traverse(root->right);
// 后序位置执行逻辑
cout << root->val;
return;
}
合并数组的过程是类似于合并两个有序链表, 双指针维护比较位置, 归并后的数组需要另外开一个数组 tmp 去保存, 然后再将 tmp 复制回原来的数组对应的位置上, 完整的归并排序代码如下:
vector<int> tmp;
void merge(vector<int>& nums, int low, int high, int mid) {
int i = low;
int j = mid + 1;
int cnt = 0;
while (i <= mid && j <= high) {
if (nums[i] <= nums[j]) {
tmp[cnt++] = nums[i++];
} else {
tmp[cnt++] = nums[j++];
}
}
while (i <= mid) {
tmp[cnt++] = nums[i++];
}
while (j <= high) {
tmp[cnt++] = nums[j++];
}
for (int i = 0; i < cnt; ++i) {
nums[low + i] = tmp[i];
}
}
void mergeSort(vector<int>& nums, int low, int high) {
// 处理单个元素的场景
if (low >= high)
return;
int mid = low + (r - l) / 2;
// 递归排序左侧的
mergeSort(nums, low, mid);
// 递归排序右侧的
mergeSort(nums, mid + 1, high);
merge(nums, low, high, mid);
return;
}
912.排序数组
给你一个整数数组 nums,请你将该数组升序排列。
示例 1:输入:nums = [5,2,3,1] 输出:[1,2,3,5]
示例 2:输入:nums = [5,1,1,2,0,0] 输出:[0,0,1,1,2,5]
class Solution {
private:
vector<int> tmp;
public:
void merge(vector<int>& nums, int low, int high, int mid) {
int i = low;
int j = mid + 1;
int cnt = 0;
while (i <= mid && j <= high) {
if (nums[i] <= nums[j]) {
tmp[cnt++] = nums[i++];
} else {
tmp[cnt++] = nums[j++];
}
}
while (i <= mid) {
tmp[cnt++] = nums[i++];
}
while (j <= high) {
tmp[cnt++] = nums[j++];
}
for (int k = 0; k < cnt; ++k) {
nums[low + k] = tmp[k];
}
}
void mergeSort(vector<int>& nums, int low, int high) {
if (low >= high) return;
int mid = low + (high - low) / 2;
mergeSort(nums, low, mid);
mergeSort(nums, mid + 1, high);
merge(nums, low, high, mid);
return;
}
vector<int> sortArray(vector<int>& nums) {
tmp.resize(nums.size(), 0);
mergeSort(nums, 0, nums.size() - 1);
return nums;
}
};
剑指 Offer 51.数组中的逆序对
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。
示例 1: 输入: [7,5,6,4] 输出: 5
分析:
1.归并排序中, 任何合并的过程里面, 我们本质是把任何逆序的元素排成有序的, 例如当前待合并的是
左侧 list=[8,12,16,22,100];
右侧 list=[9,26,55,64,91];
比较过程如下
8 < 9 放入8
12 > 9 放入9 产生了cnt个逆序对(12, 9) (16, 9) (22, 9) (100, 9), cnt = mid - i + 1
12 < 26 放入12
16 < 26 放入16
22 < 26 放入22
100 > 26 放入26 产生了1个逆序对(100, 26) cnt = mid - i + 1
当nums[i] > nums[j]的时候, 算产生的逆序对的个数为 cnt = (mid - i + 1) 个逆序对
class Solution {
private:
vector<int> tmp;
int ans;
public:
void merge(vector<int>& nums, int low, int high, int mid) {
int i = low;
int j = mid + 1;
int cnt = 0;
while (i <= mid && j <= high) {
if (nums[i] <= nums[j]) {
tmp[cnt++] = nums[i++];
} else {
ans += mid - i + 1;
tmp[cnt++] = nums[j++];
}
}
while (i <= mid) {
tmp[cnt++] = nums[i++];
}
while (j <= high) {
tmp[cnt++] = nums[j++];
}
for (int k = 0; k < cnt; ++k) {
nums[low + k] = tmp[k];
}
return;
}
void mergeSort(vector<int>& nums, int low, int high) {
if (low >= high) return;
int mid = low + (high - low) / 2;
mergeSort(nums, low, mid);
mergeSort(nums, mid + 1, high);
merge(nums, low, high, mid);
return;
}
int reversePairs(vector<int>& nums) {
tmp.resize(nums.size(), 0);
ans = 0;
mergeSort(nums, 0, nums.size()-1);
return ans;
}
};
315.计算右侧小于当前元素的个数
给你一个整数数组 nums ,按要求返回一个新数组 counts 。数组 counts 有该性质: counts[i] 的值是 nums[i] 右侧小于 nums[i] 的元素的数量。
示例 1:输入:nums = [5,2,6,1] 输出:[2,1,1,0]
解释:
5 的右侧有 2 个更小的元素 (2 和 1)
2 的右侧仅有 1 个更小的元素 (1)
6 的右侧有 1 个更小的元素 (1)
1 的右侧有 0 个更小的元素
示例 2:输入:nums = [-1] 输出:[0]
示例 3:输入:nums = [-1,-1] 输出:[0,0]
分析:
1.要统计所有比x小的元素个数, 需要到采用逆序的归并操作, 为什么这里要用逆序归并而不是顺序的归并呢? 因为只有逆序才能将所有的比x元素小的元素放在右侧的集合
左侧 list=[100,22,16,12,8]
右侧 list=[91,64,55,26,9]
比较过程如下
100>91, 增加cnt个比100小的元素, 然后放入100, cnt = high - j + 1
22<91,
22<55,
22<25,
22>9,
16>9,
12>9,
class Solution {
private:
vector<int> res;
public:
void merge(vector<pair<int, int>>& numsWithIdx, int low, int high, int mid) {
int i = low;
int j = mid + 1;
int cnt = 0;
vector<pair<int, int>> tmp(high - low + 1);
while (i <= mid && j <= high) {
if (numsWithIdx[i].first > numsWithIdx[j].first) {
res[numsWithIdx[i].second] += high - j + 1;
tmp[cnt++] = numsWithIdx[i++];
} else {
tmp[cnt++] = numsWithIdx[j++];
}
}
while (i <= mid) {
tmp[cnt++] = numsWithIdx[i++];
}
while (j <= high) {
tmp[cnt++] = numsWithIdx[j++];
}
for (int k = 0; k < cnt; ++k) {
numsWithIdx[low + k] = tmp[k];
}
return;
}
void mergeSort(vector<pair<int, int>>& numsWithIdx, int low, int high) {
if (low >= high) return;
int mid = low + (high - low) / 2;
mergeSort(numsWithIdx, low, mid);
mergeSort(numsWithIdx, mid + 1, high);
merge(numsWithIdx, low, high, mid);
return;
}
vector<int> countSmaller(vector<int>& nums) {
vector<pair<int, int>> numsWithIdx;
for (int i = 0; i < nums.size(); ++i) {
numsWithIdx.emplace_back(nums[i], i);
}
res.resize(nums.size(), 0);
mergeSort(numsWithIdx, 0, nums.size()-1);
return res;
}
};
493.翻转对
给定一个数组 nums ,如果 i < j 且 nums[i] > 2*nums[j] 我们就将 (i, j) 称作一个重要翻转对。
你需要返回给定数组中的重要翻转对的数量。示例 1: 输入: [1,3,2,3,1] 输出: 2
示例 2: 输入: [2,4,3,5,1] 输出: 3
分析:
1.利用归并排序后的结果先统计翻转对的个数, 然后继续归并排序;
左侧 list=[8,12,16,22,100];
右侧 list=[9,26,55,64,91];
8 9
12 9
16 9
22 9
22 26
class Solution {
private:
vector<int> tmp;
int ans;
public:
void mergeSort(vector<int>& nums, int low, int high) {
if (low >= high) return;
int mid = low + (high - low) / 2;
mergeSort(nums, low, mid);
mergeSort(nums, mid + 1, high);
// 先算翻转对
int i = low;
int j = mid + 1;
while (i <= mid) {
while (j <= high && (long long) nums[i] > (long long) nums[j] * 2) {
++j;
}
ans += j - (mid + 1);
++i;
}
// 再归并
int p1 = low;
int p2 = mid + 1;
int cnt = 0;
while (p1 <= mid && p2 <= high) {
if (nums[p1] <= nums[p2]) {
tmp[cnt++] = nums[p1++];
} else {
tmp[cnt++] = nums[p2++];
}
}
while (p1 <= mid) {
tmp[cnt++] = nums[p1++];
}
while (p2 <= high) {
tmp[cnt++] = nums[p2++];
}
for (int k = 0; k < cnt; ++k) {
nums[low + k] = tmp[k];
}
return;
}
int reversePairs(vector<int>& nums) {
tmp.resize(nums.size(), 0);
ans = 0;
mergeSort(nums, 0, nums.size()-1);
return ans;
}
};
327.区间和的个数
给你一个整数数组 nums 以及两个整数 lower 和 upper 。求数组中,值位于范围 [lower, upper] (包含 lower 和 upper)之内的 区间和的个数 。
区间和 S(i, j) 表示在 nums 中,位置从 i 到 j 的元素之和,包含 i 和 j (i ≤ j)。示例 1:输入:nums = [-2,5,-1], lower = -2, upper = 2 输出:3 解释:存在三个区间:[0,0]、[2,2] 和 [0,2] ,对应的区间和分别是:-2 、-1 、2 。
示例 2:输入:nums = [0], lower = 0, upper = 0 输出:1
分析:
1.区间和首先需要转换一下问题, 给定两个已经升序排列的数组n1, n2, 找出下标 (i,j) 满足 n2(j)-n1(i) 属于 [lower, upper], 这个问题称为 [辅助问题]
2.对于上述问题, 用双指针的方法可以实现, 维护n2的两个指针l和r, l和r都指向n2的第一个元素
3.只考察 n1 的第1个元素, 移动 n2 的 l 指针直到 n2[l] >= n1[0] + lower, 然后 l 指针后面的所有元素均满足 >= n1[0] + lower; l 停下来不动, 再动r, 直到 n2[r] > n1[0] + upper, 然后 r 左边的元素全部满足 <= n1[0] + upper, 然后区间[l, r)内所有的下标 j 都满足 n2[j] - n1[0] 属于 [lower, upper] 区间
4.然后考察 n1 的第2个元素, 移动方法同上; 然后考察 n1 的第 3 个元素…; 最终我们统计到所有满足条件的下标对的数量
5.考察 [辅助问题] 和 [原问题] 之间的关系, 采用归并排序的方式, 得到两个数组排序后的形式以及对应的下标对的数量; 对于原数组而言, 若要找出全部的下标对的数量, 需要再额外找出做左端点在左侧数组, 右端点在右侧数组的下标对数量
6.计算区间之和的时候用前缀和数组的技巧, 回顾下 preSum 这个数组构建是
preSum[0] = 0
preSum[1] = nums[0]
preSum[2] = nums[0] + nums[1]
preSum[3] = nums[0] + nums[1] + nums[2]
preSum[4] = nums[0] + nums[1] + nums[2] + nums[3]
preSum[n] = nums[0] + .. + nums[n-1]
sumRange(1, 3) = preSum(4) - preSum(1) = nums[1] + nums[2] + nums[3]
sumRange(i, j) = preSum(j+1, i)
class Solution {
public:
int mergeSortCount(vector<long>& sum, int low, int high, int lower, int upper) {
if (low >= high) {
return 0;
}
int mid = low + (high - low) / 2;
int leftCnt = mergeSortCount(sum, low, mid, lower, upper);
int rightCnt = mergeSortCount(sum, mid+1, high, lower, upper);
int ret = leftCnt + rightCnt;
int i = low;
int l = mid + 1;
int r = mid + 1;
while (i <= mid) {
while (l <= high && sum[l] - sum[i] < lower) ++l;
while (r <= high && sum[r] - sum[i] <= upper) ++r;
ret += (r - l);
++i;
}
vector<long> tmp(high - low + 1);
int p1 = low;
int p2 = mid + 1;
int cnt = 0;
while (p1 <= mid && p2 <= high) {
if (sum[p1] <= sum[p2]) {
tmp[cnt++] = sum[p1++];
} else {
tmp[cnt++] = sum[p2++];
}
}
while (p1 <= mid) {
tmp[cnt++] = sum[p1++];
}
while (p2 <= high) {
tmp[cnt++] = sum[p2++];
}
for (int k = 0; k < cnt; ++k) {
sum[low + k] = tmp[k];
}
return ret;
}
int countRangeSum(vector<int>& nums, int lower, int upper) {
long sum = 0;
vector<long> preSum{0};
for (auto& ele: nums) {
sum += ele;
preSum.emplace_back(sum);
}
return mergeSortCount(preSum, 0, preSum.size()-1, lower, upper);
}
};
转载请注明来源, from goldandrabbit.github.io