【Leetcode每日一题】 分治 - 计算右侧小于当前元素的个数(难度⭐⭐⭐)(77)

1. 题目解析

题目链接:315. 计算右侧小于当前元素的个数

这个问题的理解其实相当简单,只需看一下示例,基本就能明白其含义了。

2.算法原理

算法步骤
  1. 初始化
    • 定义两个全局数组:index 用于存储原始数组元素的下标,ret 用于存储每个位置对应的逆序对数量。
    • 在 countSmaller 函数中,初始化这两个数组,并为它们分配与输入数组 nums 相同大小的空间。
  2. 归并排序与逆序对计数
    • 设计一个递归的归并排序函数 mergeSort,该函数接受数组 nums、左边界 left 和右边界 right
    • 在 mergeSort 函数中,首先检查递归的基本情况,即当 left >= right 时直接返回。
    • 否则,将数组划分为左右两个子数组,并递归地对它们进行归并排序和逆序对计数。
  3. 合并与计数
    • 当合并两个已排序的子数组时,使用两个临时数组 numsTmp 和 indexTmp 分别存储排序后的元素和对应的下标。
    • 使用两个指针 cur1 和 cur2 分别指向左右子数组的起始位置,以及一个指针 dest 指向临时数组的起始位置。
    • 在合并过程中,比较 nums[cur1] 和 nums[cur2] 的大小,并根据比较结果执行以下操作:
      • 如果 nums[cur1] <= nums[cur2],则将 nums[cur1] 和 index[cur1] 添加到临时数组中,并更新 cur1 和 dest。同时,将 ret[index[cur1]] 增加 cur2 - mid - 1(因为右子数组中当前位置之前的所有元素都比 nums[cur1] 小)。
      • 如果 nums[cur1] > nums[cur2],则直接将 nums[cur2] 和 index[cur2] 添加到临时数组中,并更新 cur2 和 dest
    • 合并完成后,将临时数组的内容复制回原数组 nums 和 index
  4. 返回结果
    • 在 countSmaller 函数中,调用 mergeSort 函数对整个数组进行排序和逆序对计数。
    • 返回存储逆序对数量的数组 ret
关键点
  • 全局数组:使用全局数组 index 和 ret 来存储下标和逆序对数量,确保在递归过程中能够正确地传递和更新这些信息。
  • 归并排序变种:通过修改归并排序的合并过程,在合并两个有序子数组时同时统计逆序对的数量。
  • 下标绑定:将元素与其在原始数组中的下标绑定在一起,以便在归并过程中能够正确地更新逆序对数量。

3.代码编写

class Solution {
    vector<int> ret;
    vector<int> index; // 记录 nums 中当前元素的原始下标
    int tmpNums[500010];
    int tmpIndex[500010];

public:
    vector<int> countSmaller(vector<int>& nums) {
        int n = nums.size();
        ret.resize(n);
        index.resize(n);
        for (int i = 0; i < n; i++)
            index[i] = i;

        mergeSort(nums, 0, n - 1);
        return ret;
    }
    void mergeSort(vector<int>& nums, int left, int right) {
        if (left >= right)
            return;
        // 1. 根据中间元素,划分区间
        int mid = (left + right) >> 1;
        // [left, mid] [mid + 1, right]
        // 2. 先处理左右两部分
        mergeSort(nums, left, mid);
        mergeSort(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 {
                ret[index[cur1]] += right - cur2 + 1; // 重点,统计cur1后面有多少个元素比我小
                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++];
        }
        //还原
        for (int j = left; j <= right; j++) {
            nums[j] = tmpNums[j - left];
            index[j] = tmpIndex[j - left];
        }
    }
};

The Last

嗯,就是这样啦,文章到这里就结束啦,真心感谢你花时间来读。

觉得有点收获的话,不妨给我点个吧!

如果发现文章有啥漏洞或错误的地方,欢迎私信我或者在评论里提醒一声~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

每天进步亿丢丢

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值