归并排序本质上可以看作二叉树的后序遍历
里面用到的核心思想 => 分治
分:二叉树算法思想中的分解问题思想
治:链表中双指针技巧(将两条链表合并成一条有序链表)
sort首先将数组分成左半边和右半边
=> 然后分别对左右两边再sort(递归的味道)
=>最后通过merge合并左右两边数组
将问题无限细分下去,至不能再分解(base返回),好这就到底了
开始向上返回,在向上返回的过程中不断的merge,直至顶部完成任务
放张图辅助理解一下,也帮作者推广一下
冲浪看到的不错的辅助理解图
class Merge {
private:
vector<int> temp;
public:
void sort(vector<int>& nums) {
temp.resize(nums.size());
sort(nums, 0, nums.size() - 1);
}
private:
void sort(vector<int>& nums, int left, int right) {
if(left == right) return;
int mid = left + (right - left) / 2;
sort(nums, left, mid);
sort(nums, mid + 1, right);
merge(nums, left, mid, right);
}
void merge(vector<int>& nums, int left, int mid, int right) {
for(int i = left; i <= right; i++) {
temp[i] = nums[i];
}
int p1 = left, p2 = mid + 1;
for(int i = left; i <= right; i++) {
if(p1 == mid + 1) nums[i] = temp[p2++];
else if(p2 == right + 1) nums[i] = temp[p1++];
else if(temp[p1] < temp[p2]) nums[i] = temp[p1++];
else if(temp[p1] > temp[p2]) nums[i] = temp[p2++];
}
}
};
int main() {
vector<int> nums;
int n;
cin >> n;
while(n--) {
int num;
cin >> num;
nums.emplace_back(num);
}
(new Merge())->sort(nums);
for(auto num: nums) {
cout << num << " ";
}
return 0;
}
归并排序拓展应用
为什么j都是从mid+1开始讨论?
=> 这要从merge的原理说起,merge是层层向上合并的,
从i到mid+1的部分在上一次merge时就已经讨论过啦
315. 计算右侧小于当前元素的个数 - 力扣(LeetCode)
这题和归并排序的关系 => 主要在
merge
函数,我们在使用merge
函数合并两个有序数组的时候,其实是可以知道一个元素nums[i]
后边有多少个元素比nums[i]
小的。
在层层向上merge的时候每个子数组都是有序且为升序的
分为左右两个子数组去说吧 => 从 i 开始到mid是满足 nums[ i ] < num[ ]
=> 所以 j 从mid + 1 开始找比nums[ i ]小的区间
为什么count数组中的值的merge层层向上累加的时候为什么是一个累加的状态呢
=>你要明白现在处于一个升序的状态,你还要向右找比较小的数
在merge层层向上的时候讨论处理的数是越来越多的(=> 这就是为什么累加,有的数在前几次的merge过程中没有被加上),但是不至于重复 (=> 因为比较小的数,在merge后就被放到左边去啦,升序嘛)
class Solution {
public:
vector<pair<int,int>> help;
vector<int> count;
vector<int> countSmaller(vector<int>& nums) {
int n = nums.size();
help.resize(n);
count.resize(n);
vector<pair<int,int>> arr;
for(int i=0;i<n;i++){
pair<int,int> temp(i, nums[i]);
arr.push_back(temp);
}
process(arr,0,n-1);
return count;
}
void process(vector<pair<int,int>>& arr,int left,int right){
if(left==right) return;
int mid = left+(right-left)/2;
process(arr,left,mid);
process(arr,mid+1,right);
merge(arr,left,mid,right);
}
void merge(vector<pair<int,int>>& arr,int left,int mid,int right){
for(int i=left;i<=right;i++){
help[i] = arr[i];
}
int i = left, j = mid+1;
for(int p=left;p<=right;p++){
if(i==mid+1) arr[p] = help[j++];
else if(j==right+1){
arr[p] = help[i++];
count[arr[p].first] += j-mid-1;
}else if(help[i].second>help[j].second) arr[p] = help[j++];
else{
arr[p] = help[i++];
count[arr[p].first] += j-mid-1;
}
}
}
};
和上一题类似,不过条件从nums[i] > nums[j] => nums[i] > 2 * nums[j]
这次就不能讨巧在双指针技巧里面加东西啦 => 在双指针之前单独写逻辑讨论
class Solution {
public:
int count = 0;
vector<int> temp;
int reversePairs(vector<int>& nums) {
int n = nums.size();
temp.resize(n);
process(nums,0,n-1);
return count;
}
void process(vector<int>& nums,int left,int right){
if(left==right) return;
int mid = left+(right-left)/2;
process(nums,left,mid);
process(nums,mid+1,right);
merge(nums,left,mid,right);
}
void merge(vector<int>& nums,int left,int mid,int right){
for(int i = left; i <= right; i++) temp[i] = nums[i];
int i = left, j = mid + 1;
while(i <= mid) {
while(j <= right && (long long)nums[i] > (long long)nums[j] * 2) j++;
count += j - mid - 1;
i++;
}
i = left, j = mid + 1;
for(int p = left; p <= right; p++){
if(i == mid + 1) nums[p] = temp[j++];
else if(j == right + 1) nums[p] = temp[i++];
else if(temp[i] > temp[j]) nums[p] = temp[j++];
else nums[p] = temp[i++];
}
}
};
区间和 => 前缀和数组技巧
class Solution {
public:
int count = 0;
int lower, upper;
vector<long> temp;
int countRangeSum(vector<int>& nums, int lower, int upper) {
this->lower = lower;
this->upper = upper;
int n = nums.size();
temp.resize(n + 1);
vector<long> preSum(n + 1, 0);
for(int i = 0; i < n; i++) {
preSum[i + 1] = preSum[i] + (long)nums[i];
}
process(preSum, 0, n);
return count;
}
void process(vector<long>& nums, int left, int right) {
if(left == right) return;
int mid = left + (right - left) / 2;
process(nums, left, mid);
process(nums, mid + 1, right);
merge(nums, left, mid, right);
}
void merge(vector<long>& nums, int left, int mid, int right) {
for(int i = left; i <= right; i++) {
temp[i] = nums[i];
}
int start = mid + 1, end = mid + 1;
for(int i = left; i <= mid; i++) {
// 维护左闭右开区间 [start, end) 中的元素和 nums[i] 的差在 [lower, upper] 中
while(start <= right && nums[start] - nums[i] < lower) start++;
while(end <= right && nums[end] - nums[i] <= upper) end++;
count += end - start;
}
int i = left, j = mid + 1;
for(int p = left; p <= right; p++) {
if(i == mid + 1) nums[p] = temp[j++];
else if(j == right + 1) nums[p] = temp[i++];
else if(temp[i] < temp[j]) nums[p] = temp[i++];
else nums[p] = temp[j++];
}
}
};