题目链接
计算右侧小于当前元素的个数
题目描述
注意点
- counts[i] 的值是 nums[i] 右侧小于 nums[i] 的元素的数量
解答思路
- 本题是交易逆序对的总数的扩展,可以先进入交易逆序对的总数了解,本题与交易逆序对的总数的区别在于需要记录每个元素对逆序对的贡献
- 第一个思路是桶排序,从后往前遍历数组,将数组中的元素放进对应桶中,遍历到某个元素时,根据前缀和计算其右侧小于该元素的个数。但是桶排序要求元素最好在某个较小的区间内,否则需要创建大量空间作为桶,所以还需要提前将数组去重后对每个数字取新的映射,保证其处于某段连续的空间内,使用桶排序时如果有大量相同的元素时间比较快,如果数字比较分散则时间复杂度并不理想
- 另一个思路是归并排序,为了方便记录每个元素对逆序对的贡献,创建一个数据结构Pointer同时存储元素值和右侧小于当前元素的个数,在归并排序时,找到右区间比Pointer.val小的元素,更新Pointer.smallerCount。需要注意的是,每次更新Pointer.smallerCount时,其增长量并不是1,而是右区间所以比其小的元素的总数,也就是找到右区间第一个比左区间当前元素大的元素后右区间此刻的指针相较于初始位置移动的距离
代码
方法一:
class Solution {
public List<Integer> countSmaller(int[] nums) {
List<Integer> res = new ArrayList<>(nums.length);
// 去重
Set<Integer> set = new HashSet<>();
for (int num : nums) {
set.add(num);
}
int n = set.size();
// 排序
int index = 0;
int[] uniqueNums = new int[n];
for (int num : set) {
uniqueNums[index] = num;
index++;
}
Arrays.sort(uniqueNums);
// 哈希表映射,key->实际值,value->排序后所处的数组下标
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < n; i++) {
map.put(uniqueNums[i], i);
}
int[] smallerArr = new int[nums.length];
int[] barrelArr = new int[n];
for (int i = nums.length - 1; i >= 0; i--) {
int barrelIdx = map.get(nums[i]);
barrelArr[barrelIdx] += 1;
int smallerSum = 0;
for (int j = 0; j < barrelIdx; j++) {
smallerSum += barrelArr[j];
}
smallerArr[i] = smallerSum;
}
for (int num : smallerArr) {
res.add(num);
}
return res;
}
}
方法二:
class Solution {
public List<Integer> countSmaller(int[] nums) {
int n = nums.length;
Pointer[] pointerArr = new Pointer[n];
for (int i = 0; i < n; i++) {
Pointer pointer = new Pointer();
pointer.val = nums[i];
pointer.smallerCount = 0;
pointerArr[i] = pointer;
}
mergeSort(pointerArr, 0, n - 1);
List<Integer> res = new ArrayList<>(n);
for (Pointer pointer : pointerArr) {
res.add(pointer.smallerCount);
}
return res;
}
public Pointer[] mergeSort(Pointer[] pointerArr, int left, int right) {
if (left > right) {
return null;
}
if (left == right) {
return new Pointer[] {pointerArr[left]};
}
int mid = (left + right) / 2;
int len = right - left + 1;
Pointer[] leftArr = mergeSort(pointerArr, left, mid);
Pointer[] rightArr = mergeSort(pointerArr, mid + 1, right);
Pointer[] mergeArr = new Pointer[len];
int leftIdx = 0, rightIdx = 0;
while (leftIdx < leftArr.length || rightIdx < rightArr.length) {
// 左区间已遍历完,右区间数组后续值都比左区间大
if (leftIdx >= leftArr.length) {
mergeArr[leftIdx + rightIdx] = rightArr[rightIdx++];
continue;
}
// 找到左区间比右区间哪些数更大
while (rightIdx < rightArr.length && leftArr[leftIdx].val > rightArr[rightIdx].val) {
mergeArr[leftIdx + rightIdx] = rightArr[rightIdx++];
}
leftArr[leftIdx].smallerCount += rightIdx;
mergeArr[leftIdx + rightIdx] = leftArr[leftIdx++];
}
return mergeArr;
}
}
class Pointer {
public int val;
public int smallerCount;
}
关键点
- 桶排序的思想
- 前缀和的思想
- 归并排序的思想
- 使用更加灵活的数据结构存储数据以节省时间