分治(计算右侧小于当前元素的个数)(7)

https://blog.csdn.net/2601_95366422/article/details/159045777

上节课链接

一.题目

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

二.思路讲解

2.1 思路讲解

本题与上一题(逆序对总数)的思路一脉相承 ,本质上就是求每个元素右侧比它小的元素个数 ,即降序合并 视角下的逆序对问题,也就是我们之前提到的**"后面几个比它小"** 的应用。不过,本题的难度在于需要返回一个数组 ,记录每个元素对应的个数,而不仅仅是总数。因此,在统计过程中,我们必须精准地将每个逆序对归因到具体的元素上

为了实现这一点,我们需要引入一个下标数组 index,用来记录每个元素在原始数组中的位置。在归并排序的过程中,元素的位置会不断变化(因为要排序),但我们需要始终知道当前元素原本是哪个,这样当发现一个逆序对时,才能正确累加到对应的结果位置上。具体做法是:

  • 初始化一个与原始数组等长的结果数组 v,全部置为 0。

  • 创建一个下标数组 index,初始时 index[i] = i,表示第 i 个元素的下标。

  • 在进行归并排序时,我们不仅对元素值进行排序,同时也要对下标 进行相应的交换,使得下标与元素始终绑定即,当交换两个元素时,它们对应的下标也同步交换。

  • 在合并过程中,采用降序合并 的策略,这样当左边元素大于右边元素时,就可以统计右边剩余的元素个数,并累加到左边元素对应的结果中。具体来说:

    • 使用指针 cur1cur2 分别遍历左区间和右区间(降序有序)。

    • 如果左边元素大于右边,说明左边元素大于右边当前及之后的所有元素(因为右区间降序,右边当前是最大的),那么左边元素对应的结果应加上右边剩余的元素个数**(即 right - cur2 + 1)** ,然后将左边元素放入临时数组,移动 cur1

    • 否则,将右边元素放入临时数组,移动 cur2

  • 同时,临时数组不仅要存元素值,还要存对应的下标,以保证还原时下标正确。

三.代码演示

cpp 复制代码
class Solution 
{
    int tmpNums[500005];//临时数组
    int tmpIndex[500005];//临时数组
    vector<int>v;//最后要返回的数组
    vector<int>index;//对应nums元素的元素下标

public:
    vector<int> countSmaller(vector<int>& nums) 
    {
        int n = nums.size();
        v.resize(n);
        index.resize(n);
        for(int i = 0;i < n;i++)
        {
            index[i] = i;//index1的元素对应的是nums原始下标
        }
        mermageSort(nums,0,n-1);
        return v;
    }
    void mermageSort(vector<int>& nums,int left,int right)
    {
        if(left >= right) return;
        //1.取中点
        int mid = left + (right - left)/2;

        //2.左右区间排序
        mermageSort(nums,left,mid);
        mermageSort(nums,mid+1,right);

        //3.合并数组
        int cur1 = left,cur2 = mid + 1,i = 0;
        while(cur1 <= mid && cur2 <= right)
        {
            if(nums[cur1] <= nums[cur2])
            {
                tmpNums[i] = nums[cur2];//更新元素
                tmpIndex[i++] = index[cur2++];//更新下标
            }
            else
            {
                v[index[cur1]] += right - cur2 + 1 ;//更新结果
                tmpNums[i] = nums[cur1];//更新元素
                tmpIndex[i++] = index[cur1++];//更新下标
            }
        }
        while(cur1 <= mid)
        {
            tmpNums[i] = nums[cur1];
            tmpIndex[i++] = index[cur1++];
        }
        while(cur2 <= right)
        {
            tmpNums[i] = nums[cur2];
            tmpIndex[i++] = index[cur2++];
        }

        //4.还原数组
        for(int i = left;i <= right;i++)
        {
            nums[i] = tmpNums[i - left];
            index[i] = tmpIndex[i - left];
        }
    }
};

四.代码讲解

一、初始化结果数组和下标数组

本题要求返回每个元素右侧比它小的元素个数,因此我们需要一个与原始数组等长的结果数组 v,初始化为全 0 。同时,为了在排序过程中追踪每个元素的原始位置,我们引入一个下标数组 index,初始时 index[i] = i,表示第 i 个元素在原数组中的下标。在归并排序的过程中,元素值会移动,但我们必须保证下标与元素始终绑定,这样当统计逆序对时,才能正确累加到对应的结果位置上。

二、递归函数与临时数组

我们定义一个归并排序函数,参数包括待排序区间 [left, right]同时,需要准备一个临时数组 tmpNums 用于存储合并过程中的元素值,以及一个临时下标数组 tmpIdx 用于同步存储对应的下标。

三、递归划分与终止条件
  • 终止条件 :当 left >= right 时,区间内只有一个元素或为空,直接返回。

  • 划分中点mid = left + (right - left) / 2,将区间分为左区间 [left, mid] 和右区间 [mid+1, right]

  • 递归排序:先递归处理左区间,再递归处理右区间。递归返回后,左右区间各自内部有序,并且左右区间内部的"右侧小于当前元素"已经统计完毕。

四、合并过程(降序合并)

在合并左右两个有序区间时,我们采用降序合并的策略,即合并后的序列从大到小排列。这样做的目的是为了在比较时方便统计右边比当前元素小的个数。具体步骤如下:

  1. 指针初始化

    • cur1 指向左区间起点 left

    • cur2 指向右区间起点 mid + 1

    • i 指向临时数组的起始位置(0)。

  2. 循环比较 :当 cur1 <= midcur2 <= right 时,比较 nums[cur1]nums[cur2]

    • 如果 nums[cur1] > nums[cur2] :说明左区间当前元素大于右区间当前元素。由于右区间是降序,cur2 及其后面的所有元素都小于 nums[cur1],因此对于左区间这个元素,右侧比它小的元素个数应加上右区间剩余元素的数量,即 right - cur2 + 1 。将这个数量累加到结果数组中该元素对应的位置 v[index[cur1]] 上。然后,将左区间当前元素及其下标存入临时数组cur1++i++

    • 否则(nums[cur1] <= nums[cur2]):此时左区间元素不大于右区间元素,不产生逆序对,直接将右区间当前元素及其下标存入临时数组,cur2++i++

  3. 处理剩余元素

    • 如果左区间有剩余,将剩余元素及其下标依次存入临时数组。

    • 如果右区间有剩余,同样将剩余元素及其下标存入临时数组。

五、将临时数组还原回原数组

合并完成后,临时数组 tmpNumstmpIdx 中存储了当前区间降序排列的元素及其对应的原始下标。我们需要将它们拷贝回原数组 nums 和下标数组 index 的对应位置(从 leftright)。这一步保证了在递归返回后,原数组在 [left, right] 范围内是降序的,且下标与元素绑定正确。

六、关键细节
  • 下标绑定:在整个过程中,我们必须确保每次交换元素时,对应的下标也随之交换,否则统计会错位。
相关推荐
benpaodeDD2 小时前
JDBC内容学习
学习
EmmaXLZHONG2 小时前
Django By Example - 学习笔记
笔记·python·学习·django
迷海2 小时前
C++内存对齐
开发语言·c++
cxr8282 小时前
细胞球运动追踪的卡尔曼滤波与力场插值算法 —— 活体内微米级颗粒实时定位与轨迹预测系统
算法
炘爚2 小时前
C++(流类:istream /ostream/istringstream /ostringstream)
开发语言·c++·算法
!停2 小时前
C++入门—内存管理
java·jvm·c++
A.A呐2 小时前
【C++第二十五章】智能指针
c++
塞北山巅2 小时前
Windows 下基于 MSYS2 搭建 C++ 开发环境:从安装到配置全指南
开发语言·c++·windows
海参崴-2 小时前
C语言与C++语言发展历史详解
java·c语言·c++