《算法题讲解指南:优选算法-分治-归并》--49.计算右侧小于当前元素的个数,50.翻转对

🔥小叶-duck个人主页

❄️个人专栏《Data-Structure-Learning》

《C++入门到进阶&自我学习过程记录》《算法题讲解指南》--优选算法

未择之路,不须回头
已择之路,纵是荆棘遍野,亦作花海遨游


目录

49.计算右侧小于当前元素的个数

题目链接:

题目描述:

题目示例:

解法(归并排序):

算法思路:

C++算法代码:

算法总结及流程解析:

50.翻转对

题目链接:

题目描述:

题目示例:

题目解析:

解法(归并排序):

算法思路:

算法总结及流程解析:

结束语


49.计算右侧小于当前元素的个数

题目链接:

315. 计算右侧小于当前元素的个数 - 力扣(LeetCode)

题目描述:

题目示例:

解法(归并排序):

算法思路:

这一道题的解法与 求数组中的逆序对的解法是类似的,但是这一道题要求的不是求总的个数,而是要返回一个数组,记录每一个元素的右边有多少个元素比自己小。

但是在我们归并排序的过程中,元素的下标是会跟着变化 的,因此我们需要一个辅助数组 ,来将数组元素和对应的下标绑定在一起归并,也就是再归并元素的时候,顺势将下标也转移到对应的位置上。

由于我们要快速统计出某一个元素后面有多少个比它小的,因此我们可以利用求逆序对的第二种方法。

C++算法代码:

cpp 复制代码
class Solution {
public:
    vector<int> counts;
    vector<int> index; //记录nums中当前元素的原始下标
    vector<int> tmp;
    vector<int> tmpi; //临时记录合并后对应元素位置变化下标随之变化的数组(保证元素和原始下标始终绑定)

    vector<int> countSmaller(vector<int>& nums) 
    {
        counts.resize(nums.size());
        index.resize(nums.size());
        tmp.resize(nums.size());
        tmpi.resize(nums.size());
        //初始化index,使得nums每个元素下标和index一一对应
        for(int i = 0; i <nums.size(); i++)
        {
            index[i] = i;
        }
        mergesort(nums, 0, nums.size() - 1);
        return counts;
    }

    void mergesort(vector<int>& nums, int left, int right)
    {
        if(left == right)
        {
            return;
        }
        int mid = (right - left) / 2 + left;
        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])
            {
                counts[index[cur1]] += right - cur2 + 1; //重点
                //因为index[cur1]始终是cur1当前元素的原始下标,所以不管当前元素位置怎么变化
                //counts[index[cur1]]的位置都不会变
                tmpi[i] = index[cur1];
                tmp[i++] = nums[cur1++];
            }
            else
            {
                tmpi[i] = index[cur2];
                tmp[i++] = nums[cur2++];
            }
        }
        while(cur1 <= mid)
        {
            tmpi[i] = index[cur1];
            tmp[i++] = nums[cur1++];    
        }
        while(cur2 <= right)
        {
            tmpi[i] = index[cur2];
            tmp[i++] = nums[cur2++];
        }

        for(int j = left; j <= right; j++)
        {
            nums[j] = tmp[j - left];
            index[j] = tmpi[j - left];
        }
    }
};

算法总结及流程解析:

50.翻转对

题目链接:

493. 翻转对 - 力扣(LeetCode)

题目描述:

题目示例:

题目解析:

翻转对和逆序对的定义大同小异,逆序对是前面的数要大于后面的数。而翻转对是前面的一个数要大于后面某个数的两倍。因此,我们依旧可以用归并排序的思想来解决这个问题。

解法(归并排序):

算法思路:

大思路与求逆序对的思路一样,就是利用归并排序的思想,将求整个数组的翻转对的数量,转换成三部分:左半区间翻转对的数量,右半区间翻转对的数量,一左一右选择时翻转对的数量。重点就是在合并区间过程中,如何计算出翻转对的数量。

与上个问题不同的是,上一道题我们可以一边合并一遍计算,但是这道题要求的是左边元素大于右边元素的两倍,如果我们直接合并的话,是无法快速计算出翻转对的数量的。

因此我们需要在归并排序之前完成翻转对的统计

cpp 复制代码
class Solution {
public:
    vector<int> tmp;
    int count = 0;
    int reversePairs(vector<int>& nums) 
    {
        tmp.resize(nums.size());
        mergesort(nums, 0, nums.size() - 1);
        return count;
    }

    void mergesort(vector<int>& nums, int left, int right)
    {
        if(left >= right)
        {
            return;
        }
        int mid = (right - left) / 2 + left;
        mergesort(nums, left, mid);
        mergesort(nums, mid + 1, right);

        //这里需要注意:当我们左右区间排完序后,按照之前逆序对类似题目
        //我们是求值和合并数组同时进行的
        //因为左边大于右边正好就可以求值顺带可以进行合并操作
        //但是这里求助的条件是左边需要大于右边的两倍
        //也就是说如果左边大于右边我们不一定能够求值,但是必须要进行合并,就会出现问题
        //所以如果不能同时进行操作我们就将求值和合并分开操作:先进行求值再进行合并数组
        int cur1 = left, cur2 = mid + 1;
        while(cur1 <= mid && cur2 <= right)//降序操作
        {
            //当左边大于右边的两边才会进行求值
            if(nums[cur1] > (long long)2 * nums[cur2])
            {
                count += right - cur2 + 1;
                cur1++;
            }
            else
            {
                cur2++;
            }
        }//当出了循环说明要么cur1>mid要么cur2>right,不管哪种情况此时翻转对是求完了
         //无需像合并数组那样处理剩余部分
         
        cur1 = left;
        cur2 = mid + 1;
        int i = 0;
        while(cur1 <= mid && cur2 <= right)
        {
            if(nums[cur1] > nums[cur2])
            {
                tmp[i++] = nums[cur1++];
            }
            else
            {
                tmp[i++] = nums[cur2++];
            }
        }
        while(cur1 <= mid)
        {
            tmp[i++] = nums[cur1++];
        }
        while(cur2 <= right)
        {
            tmp[i++] = nums[cur2++];
        }

        // while(cur1 <= mid && cur2 <= right)//升序操作同样如此
        // {
        //     当左边大于右边的两边才会进行求值
        //     if(nums[cur1] > (long long)2 * nums[cur2])
        //     {
        //         count += mid - cur1 + 1;
        //         cur2++;
        //     }
        //     else
        //     {
        //         cur1++;
        //     }
        // }
        // cur1 = left;
        // cur2 = mid + 1;
        // int i = 0;
        // while(cur1 <= mid && cur2 <= right)
        // {
        //     if(nums[cur1] > nums[cur2])
        //     {
        //         tmp[i++] = nums[cur2++];
        //     }
        //     else
        //     {
        //         tmp[i++] = nums[cur1++];
        //     }
        // }
        // 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];
        }
    }
};

算法总结及流程解析:

结束语

到此,49.计算右侧小于当前元素的个数,50.翻转对 这两道算法题就讲解完了。**49题通过维护原始下标数组,在归并过程中统计右侧较小元素数量;50题则需先统计满足条件的翻转对数量,再进行归并排序。**希望大家能有所收获!

相关推荐
_Twink1e1 小时前
[算法竞赛]八、排序、排列
数据结构·c++·笔记·算法·排序算法
im_AMBER1 小时前
Leetcode 137 组合 | 电话号码的字母组合
开发语言·算法·leetcode·深度优先·剪枝
Alex艾力的IT数字空间1 小时前
OCR 原理:从像素到文本的智能转换
数据结构·人工智能·python·神经网络·算法·cnn·ocr
仟濹2 小时前
【算法打卡day19(2026-03-11 周三)算法:打家劫舍-DP,双指针,二分查找,滑动窗口,方向控制,前缀和 】8个题
算法·leetcode·二分查找·动态规划
未来之窗软件服务2 小时前
自己写算法(十)js加密UUID保护解密——东方仙盟化神期
java·javascript·算法·代码加密·东方仙盟算法
样例过了就是过了2 小时前
LeetCode热题100 腐烂的橘子
数据结构·c++·算法·leetcode·bfs
mango_mangojuice2 小时前
C++学习笔记(list)3.6
c++·笔记·学习
X在敲AI代码2 小时前
D32次 第2题 因子化简
开发语言·c++
deng-c-f2 小时前
Linux C/C++ 学习日记(80):Kafka(八):topic会自动创建吗?
linux·c++·学习·karfka