归并思想__LeetCode数组中的逆序对&计算右侧小于当前元素的个数

这篇博客介绍了如何使用归并排序算法解决两个编程问题:数组中的逆序对计数和计算数组中每个元素右侧小于它的元素个数。在逆序对问题中,通过比较归并过程中元素的相对大小,可以实时累计逆序对的数量。而在计算右侧小于当前元素个数的问题中,通过维护元素及其原始下标的对,并在归并过程中更新计数,可以有效地得出每个元素右侧的小于它的元素个数。这两个问题都展示了归并排序在处理有序性相关问题时的高效性。
摘要由CSDN通过智能技术生成

一、剑指Offer 51 数组中的逆序对

  在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。

题目地址

示例 1:

输入: [7,5,6,4]
输出: 5

这里的思路主要就是利用了归并的思想,举个例子,对于[1,4,5,7] ,[2,3,6,8]这两个待归并的序列,
在这里插入图片描述

  当a[i] > a[j]的时候 显然 [i, m]这个闭区间的所有元素都大于a[j],此时可以将j为2时候的逆序对全部加入,个数为 m - i + 1,当i > m的时候对于当前的j来说就没有逆序对了。

在这里插入图片描述

  还有一种思路是当a[i] <= a[j]的时候,对于i来说[m+1, j - 1]这个闭区间的所有元素都是小于a[i]的,数量为j - m - 1,在 j > r 的时候对于当前i之后位置的每个元素来说都有j - m - 1 也等于r - m个逆序对。这种方式计算的就是对于当前的i,他右边的逆序对的个数,这个思想下一道题会用到。

class Solution {
    int m_count;
public:
    int reversePairs(vector<int>& nums) {
        m_count = 0;
        mergeSort(nums, 0, nums.size() - 1);
        return m_count;
    }
 
    void merge(vector<int>& a, int l, int m, int r) {
        int i = l, j = m + 1;
        vector<int> aux(r - l + 1);
        
        for(int k = 0; k < r - l + 1; k++) {
            if(i > m) {
                aux[k] = a[j++];
            }
            else if(j > r) {
                //m_count += j - m - 1;
                aux[k] = a[i++];
            }
            else if(a[i] <= a[j]) {
                //m_count += j - m - 1;
                aux[k] = a[i++];
            }
            else {
                m_count += (m - i + 1);
                aux[k] = a[j++];
            }
        }
        // while (i <= m && j <= r){
        //     if(a[i] <= a[j]){
        //         aux[k++] = a[i++];
        //     }else {
        //         aux[k++] = a[j++];
        //         m_count = m_count + (m - i + 1);
        //     }
        // }
        // while (i <= m){
        //     aux[k++] = a[i++];
        // }
        // while (j <= r){
        //     aux[k++] = a[j++];
        // }
        ::copy(aux.begin(), aux.end(), a.begin() + l);
    }

    void mergeSort(vector<int>& a, int l, int r) {
        if(l >= r) return;

        int m = l + (r - l) / 2;

        mergeSort(a, l, m);
        mergeSort(a, m + 1, r);

        merge(a, l, m, r);
    }

};

二、LeetCode 315 计算右侧小于当前元素的个数

  给定一个整数数组 nums,按要求返回一个新数组 counts。数组 counts 有该性质: counts[i] 的值是 nums[i] 右侧小于 nums[i] 的元素的数量。

题目地址

示例:

输入:nums = [5,2,6,1]
输出:[2,1,1,0] 
解释:
5 的右侧有 2 个更小的元素 (21)
2 的右侧仅有 1 个更小的元素 (1)
6 的右侧有 1 个更小的元素 (1)
1 的右侧有 0 个更小的元素

提示:
0 <= nums.length <= 10^5
-10^4 <= nums[i] <= 10^4

  这道题有个比较特殊的地方是要返回原来每个位置元素右边逆序的个数,由于在归并的过程中某个元素的下标会不断改变,那么可以思考这样的思路:将每个元素和他的初始下标用pair关联起来,在交换元素的时候就可可以查看到原始的下标,再根据上题目的第二种思路就可以得到某个元素i右边的逆序数的个数。

class Solution {
    vector<pair<int, int>> m_vpairs;
public:
    vector<int> countSmaller(vector<int>& nums) {
        int n = nums.size(), l = 0, r = n - 1;
        vector<int> ans(n);
        
        for(int i = 0; i < n; i++){
            m_vpairs.push_back({nums[i], i});
        }

        mergeSort(nums, ans, l, r);
        return ans;
    }

    void merge(vector<int>& ans, int l, int m, int r) {
        int i = l, j = m + 1;
        vector<pair<int, int>> sort_vpairs(r - l + 1);//为了节约时间,每次不必复制整个数组,只需要复制待归并的区间就行

        for(int k = 0; k < r - l + 1; k++){
            if(i > m) {
                sort_vpairs[k] = m_vpairs[j++];
            }
            else if(j > r) {
                ans[m_vpairs[i].second] += j - m - 1;
                sort_vpairs[k] = m_vpairs[i++];
            }
            else if(m_vpairs[i].first <= m_vpairs[j].first) {
                ans[m_vpairs[i].second] += j - m - 1;
                sort_vpairs[k] = m_vpairs[i++];
            }
            else{
                sort_vpairs[k] = m_vpairs[j++];
            }
        }
        ::copy(sort_vpairs.begin(), sort_vpairs.end(), m_vpairs.begin() + l);
    }

    void mergeSort(vector<int>& nums, vector<int>& ans, int l, int r) {
        if(l >= r) return;
        int m = l + (r - l) / 2;

        mergeSort(nums, ans, l, m);
        mergeSort(nums, ans, m + 1, r);

        merge(ans, l, m, r);
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值