代码:
class Solution {
int[] ret; // 用于存储结果,每个元素右边比它小的元素个数
int[] index; // 记录元素在原始数组中的下标
int[] indexTmp; // 用于临时存储合并过程中的下标
int[] tmp; // 用于临时存储合并过程中的元素值
public List<Integer> countSmaller(int[] nums) {
int n = nums.length;
ret = new int[n]; // 初始化结果数组
index = new int[n]; // 初始化下标数组
indexTmp = new int[n]; // 初始化临时下标数组
tmp = new int[n]; // 初始化临时排序数组
// 初始化 index 数组,记录每个元素的初始下标
for (int i = 0; i < n; i++) {
index[i] = i;
}
// 开始归并排序
mergeSort(nums, 0, n - 1);
// 将结果数组转化为 List 形式输出
List<Integer> result = new ArrayList<>();
for (int i = 0; i < n; i++) {
result.add(ret[i]);
}
return result;
}
// 归并排序算法
private void mergeSort(int[] nums, int left, int right) {
if (left >= right) return;
int mid = (left + right) / 2;
// 分治,递归处理左半部分和右半部分
mergeSort(nums, left, mid);
mergeSort(nums, mid + 1, right);
// 合并两个有序的子数组
int cur1 = left, cur2 = mid + 1, i = 0;
while (cur1 <= mid && cur2 <= right) {
if (nums[cur1] <= nums[cur2]) {
// 左边元素小于等于右边,不形成逆序对;大的元素放进tmp(降序归并)
tmp[i] = nums[cur2];
indexTmp[i++] = index[cur2++];
} else {
// 左边元素大于右边,形成逆序对
ret[index[cur1]] += right - cur2 + 1; // 更新逆序对的数量
tmp[i] = nums[cur1];
indexTmp[i++] = index[cur1++];
}
}
// 合并剩余的元素
while (cur1 <= mid) {
tmp[i] = nums[cur1];
indexTmp[i++] = index[cur1++];
}
while (cur2 <= right) {
tmp[i] = nums[cur2];
indexTmp[i++] = index[cur2++];
}
// 将合并后的结果写回原数组和下标数组
for (int j = left; j <= right; j++) {
nums[j] = tmp[j - left];
index[j] = indexTmp[j - left];
}
}
}
注意:
index
数组在你的程序中用于记录原始数组中元素的下标,以便在排序的过程中能够跟踪每个元素在原数组中的位置。这是为了保证在归并排序过程中,逆序对统计时能够正确记录每个元素在原数组中的位置,而不是其当前排序后的位置。
具体作用
在归并排序过程中,我们会对数组 nums
进行分割并排序,但我们关心的不是排序后的新位置,而是元素在原数组中的位置。因此,通过维护 index
数组,归并时可以:
-
正确追踪元素的位置:在归并的过程中,元素的下标会发生变化,但我们需要知道这些元素在原数组中的下标来更新逆序对的统计。所以,
index
数组帮助我们追踪每个元素的原始位置。 -
更新逆序对结果:当我们发现
nums[cur1] > nums[cur2]
时,意味着对于nums[cur1]
这个位置的元素来说,存在right - cur2 + 1
个逆序对。因此,我们通过index[cur1]
得到nums[cur1]
在原数组中的下标,然后更新ret[index[cur1]]
。