【Leetcode每日一题】 分治 - 交易逆序对的总数(难度⭐⭐⭐)(74)

1. 题目解析

题目链接:LCR 170. 交易逆序对的总数

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

2.算法原理

归并排序的基本思路

归并排序将数组从中间分成两部分,在排序的过程中,逆序对的来源分为以下三类:

  1. 左子数组内部的逆序对
  2. 右子数组内部的逆序对
  3. 跨越左右子数组的逆序对

最终的逆序对总数是这三类逆序对的总和。归并排序的整体步骤如下:

  1. 排序左子数组
  2. 排序右子数组
  3. 合并两个有序子数组
利用归并排序统计逆序数的原理

在归并排序的合并过程中,左、右子数组始终保持有序状态。我们可以利用这一特性快速统计跨越左右子数组的逆序对数量,而不必遍历所有可能的组合。

具体计算逆序数的方法

合并两个有序数组时,可以通过以下两种方式之一统计逆序数:

  1. 统计某个数之前的有多少个数比它大
  2. 统计某个数之后的有多少个数比它小

我们重点分析第一种方式的原理。

示例分析

假设两个有序数组和辅助数组为 left = [5, 7, 9]right = [4, 5, 8]help = []。通过合并的过程可以求得逆序数。定义如下变量:

  • cur1:遍历 left 数组的指针
  • cur2:遍历 right 数组的指针
  • ret:记录逆序数的计数器

合并的具体步骤如下:

  1. 第一轮left[cur1] > right[cur2]。因为 left 数组中 [cur1, 2] 区间的所有元素均大于 right[cur2],这些元素可以与 right[cur2] 构成逆序对。因此,更新 ret += 3 并将 right[cur2] 放入 help 数组,同时 cur2++

  2. 第二轮left[cur1] == right[cur2]。此时 right[cur2] 可能与 left 中的其他元素形成逆序对,因此将 left[cur1] 放入 help 数组。没有新增逆序对,不更新 ret

  3. 第三轮left[cur1] > right[cur2]。与第一轮类似,left[cur1, 2] 区间内的元素均大于 right[cur2],更新 ret += 2,并将 right[cur2] 放入 help 数组,cur2++

  4. 第四轮left[cur1] < right[cur2]left[cur1]right 中的所有元素小,不构成逆序对。直接将 left[cur1] 放入 help 数组,不更新 ret

  5. 第五轮left[cur1] > right[cur2]。此时 left 中的元素能与 right[cur2] 构成逆序对,更新 ret += 1,并将 right[cur2] 放入 help 数组。

处理剩余元素

在合并过程中,如果 left 中还有剩余元素,说明这些元素已经与 right 中的元素计算过,不会新增逆序对。直接将剩余元素放入 help 数组。如果 right 中还有剩余元素,则这些元素均比 left 中的元素大,同样不会构成逆序对。

小结

通过上述方式利用归并排序的合并过程,可以快速统计逆序数。复杂度为 O(N log N),相较于暴力解法的 O(N^2) 效率更高。

3.代码编写

class Solution {
    int tmp[50010];

public:
    int reversePairs(vector<int>& nums) {
        return mergeSort(nums, 0, nums.size() - 1);
    }
    int mergeSort(vector<int>& nums, int left, int right) {
        if (left >= right)
            return 0;
        int ret = 0;
        // 1. 找中间点,将数组分成两部分
        int mid = (left + right) >> 1;
        // [left, mid][mid + 1, right]
        // 2. 左边的个数 + 排序 + 右边的个数 + 排序
        ret += mergeSort(nums, left, mid);
        ret += mergeSort(nums, mid + 1, right);
        // 3. ⼀左⼀右的个数
        int cur1 = left, cur2 = mid + 1, i = 0;
        while (cur1 <= mid && cur2 <= right) // 升序的时候
        {
            if (nums[cur1] <= nums[cur2]) {
                tmp[i++] = nums[cur1++];
            } else {
                ret += mid - cur1 + 1;
                tmp[i++] = nums[cur2++];
            }
        }
        // 4. 处理⼀下排序
        while (cur1 <= mid)
            tmp[i++] = nums[cur1++];
        while (cur2 <= right)
            tmp[i++] = nums[cur2++];
        for (int j = left; j <= right; j++)
            nums[j] = tmp[j - left];

        return ret;
    }
};

The Last

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

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

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

  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

每天进步亿丢丢

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

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

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

打赏作者

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

抵扣说明:

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

余额充值