【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.代码编写

cpp 复制代码
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

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

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

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

相关推荐
西柚小萌新2 小时前
【岗位面试】--成都博维数孪 - 工业 AI 解决方案顾问
人工智能·面试·职场和发展
小 黄 鸡2 小时前
Flutter 面试必会知识点详解(从基础到进阶)
flutter·面试·职场和发展
Navigator_Z3 小时前
LeetCode //C - 1289. Minimum Falling Path Sum II
c语言·算法·leetcode
CCblandy3 小时前
令牌桶在 0.9 倍负载超发 19%、固定窗口边界放行 20 个:四种限流算法同构实测
后端·python·算法·性能优化·架构
mmmmath_33 小时前
LeetCode.104.二叉树的最大深度
数据结构·leetcode
xier_ran4 小时前
【infra之路】AI 编译器学习笔记(六):重拾 C++——lambda 与 MLIR 中的回调
c++·笔记·学习
YangYang9YangYan4 小时前
2027供应链计划校招:数学与应用数学专业如何把预测模型讲成业务决策?
人工智能·sql·算法
jsjzsl24 小时前
递归自改进系统(RSI)的本质是自发对称破缺:智能爆炸有界—— AI研发切记盲目投入
人工智能·python·算法
程序员清风4 小时前
最短路径算法:BFS、Dijkstra 与 Floyd
java·算法·宽度优先
梦帮科技4 小时前
Kali Linux Scapy 畸形报文构造学——深度解剖非法 TCP Flags 与反常序列号
linux·爬虫·神经网络·算法·数学建模·系统架构·模拟退火算法