最近在力扣上反复刷分治相关的题目,发现归并排序这组题特别适合用来理解"分而治之"的核心思想。今天把排序数组和交易逆序对的总数这两道题放在一起写,是因为它们本质上共享同一套底层框架:归并排序。一道题是直接考察归并排序本身的实现,另一道题则是利用归并过程中的有序性做文章。把这组题吃透,不光是学会两个题解,更重要的是建立起"分治问题怎么拆、怎么合、在哪里做手脚"的思维习惯。这篇笔记给准备面试、正在刷力扣热题100的同学一个参考,也是我自己踩坑之后的复盘总结。
1. 整体思路拆解:为什么归并排序能同时解决这两道题
1.1 归并排序的分治折叠
要理解这两道题,先得把归并排序的骨架打扎实。归并排序的思路归纳起来就两句话:先把数组不断对半分,分到不能再分,然后两两合并,在合并过程中把元素按顺序排好。这个"先拆后合"的过程,就是分治法的标准模板。
拆的过程比较简单,递归调用的每一层都取中点 mid,把左半部分和右半部分分别排序。合的过程是核心,需要借助一个临时数组,用双指针分别指向两个子数组的起始位置,比较大小后依次放入临时数组,最后拷贝回原数组。
关键点在合并阶段:左半部分和右半部分在各自内部已经有序了,所以合并时只需要 O(n) 的扫描就能完成整体排序。整个算法的时间复杂度稳定在 O(nlogn),不管输入数据原本是什么状态,都是这个量级,这正是归并排序区别于快排的重要特性。
很多人第一次写归并排序时,容易被递归过程绕晕。我的经验是:把递归当作"黑盒"。你只需要清楚两点:第一,递归调用结束后,左半部分和右半部分各自已经有序;第二,你要负责把这两个有序部分合并起来。至于递归内部怎么排序,不用在当前这一层操心。能把这个抽象思维建立起来,分治的代码就不会写得纠缠不清。
1.2 从"排序"到"逆序对"的思维跳跃
先抛出一个观察:如果数组是有序的,那么逆序对的数量就是 0;如果一个数组完全是倒序的,逆序对数量就是最大值。换句话说,排序的过程本身就天然和逆序对有关联。
那么逆序对的定义是什么?对于数组中的两个位置 i < j,如果 nums[i] > nums[j],那就构成一对逆序对。暴力解法是双重循环,O(n^2) 的时间复杂度,在大数组上完全不可行。
归并排序给了我们一个巧妙的视角:在合并左右两个有序子数组时,如果从右边取了一个元素放入临时数组,这个元素和左边剩余的所有元素都构成了逆序对。因为左边剩余的元素全部比它大,且位置都在它前面。这样每取一次右边元素,就能一次统计出一批逆序对,把所有合并过程中的统计结果累加,就是整个数组的逆序对总数。
这个转化是整个题目的精华所在,也是分治思想最漂亮的应用场景之一。排序本身和业务统计在一个框架里同时完成,时间复杂度不变,仍然是 O(nlogn),空间复杂度也同样是 O(n)。
1.3 两道题的异同与刷题顺序
把这两道题放在一起刷的原因很简单:排序数组是第一层,考察基本实现;交易逆序对的总数是第二层,在排序数组的基础上加了一层统计逻辑。我建议先写一道,再写第二道,因为第二道题可以看作是在第一道题的合并逻辑里"加一行代码"。
当然有一个很重要的区别需要注意。排序数组这道题要求我们对数组本身进行原地排序,输出排好序的结果。而逆序对题目要求的是返回一个计数结果,并不需要真的把数组排好序,但排序过程是统计的工具。理解这个差异之后,你就不会被"明明排序了却没用到排序结果"这件事困扰。
还有一个容易被忽视的点:交易逆序对的总数这道题通常要求的返回值是 int 类型,但极端情况下逆序对数量可能超过 int 范围。写代码时要根据题目要求调整返回类型,否则提交时会卡在数据范围上,这个细节在后面的常见问题里还会展开说。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心细节解析:排序数组的实现要点
2.1 递归版本的完整代码
先上排序数组这道题的经典递归实现。题目本身要求对整数数组排序,我用 Java 来写:
java复制class Solution {
public int[] sortArray(int[] nums) {
if (nums == null || nums.length < 2) {
return nums;
}
int[] temp = new int[nums.length];
mergeSort(nums, 0, nums.length - 1, temp);
return nums;
}
private void mergeSort(int[] nums, int left, int right, int[] temp) {
if (left >= right) {
return;
}
int mid = left + (right - left) / 2;
mergeSort(nums, left, mid, temp);
mergeSort(nums, mid + 1, right, temp);
