从二叉树角度看归并排序

归并排序本质上可以看作二叉树的后序遍历

里面用到的核心思想 => 分治

分:二叉树算法思想中的分解问题思想

治:链表中双指针技巧(将两条链表合并成一条有序链表)

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;
            }
        }
    }
};

493. 翻转对 - 力扣(LeetCode)

和上一题类似,不过条件从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++];
        }
    }
};

327. 区间和的个数 - 力扣(LeetCode) 

区间和 => 前缀和数组技巧

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++];
        }
    }
};

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值