题目 315. 计算右侧小于当前元素的个数
难度 困难
给定一个整数数组 nums,按要求返回一个新数组 counts。数组 counts 有该性质: counts[i] 的值是 nums[i] 右侧小于 nums[i] 的元素的数量。
示例:
输入: [5,2,6,1]
输出: [2,1,1,0]
解释:
5 的右侧有 2 个更小的元素 (2 和 1).
2 的右侧仅有 1 个更小的元素 (1).
6 的右侧有 1 个更小的元素 (1).
1 的右侧有 0 个更小的元素.
解题思路
普通遍历算法肯定超时,应该思考如何简化算法。
这里采用 归并排序 + 索引数组的算法
整个的思路是这样子的:
在归并排序的过程中,统计每个前有序数组中每个数的逆序数个数。
我们首先要知道什么是归并排序,这个在排序算法中有讲,我就不说了。
然后要在归并排序的过程中,统计每个数的逆序数的个数。
那么如何统计逆序数的个数呢?我们在归并的时候,总是将数组分割为两部分,然后将两部分进行排序,分为前有序数组和后有序数组。
求解 “逆序对” 的关键在于:当其中一个数字放进最终归并以后的有序数组中的时候,这个数字与之前看过的数字个数(或者是未看过的数字个数)可以直接统计出来,而不必一个一个数”。
而本题目的要求是:让我们求 “在一个数组的某个元素的右边,比自己小的元素的个数”
因此,我们就 应该在 “前有序数组” 的元素出列的时候,数一数 “后有序数组” 已经出列了多少元素,因为这些已经出列的元素都比当前出列的元素要小(或者等于)。下面这幅图可能比较好理解。
但是与此同时,我们也发现一个问题,归并排序后,数组的下标会发生变化,那要怎样去定位原始数组的下标呢?这里采用一个索引数组的办法。索引数组的原理是这样的:
也就是说,我们不管归并之后怎么变化,用一个临时数组来记录原数组的下标。这样在统计每个数的数量时候,就能够精确定位
总结:
1、我们借助计算 “逆序数” 的思路完成本题,关键在于这里我们只能在 “前有序数组” 出列的时候计算逆序数;这点很关键,一定要弄清楚逆序数的求法。
如果题目让我们计算 “nums[i] 左侧小于 nums[i] 的元素的数量” 可以在 “后有序数组” 出列的时候计算逆序数;
2、体会 “索引数组” 这个使用技巧。
代码
import java.util.ArrayList;
import java.util.List;
public class Solution {
private int[] temp;
private int[] counter;
private int[] indexes;
public List<Integer> countSmaller(int[] nums) {
List<Integer> res = new ArrayList<>();
int len = nums.length;
if (len == 0) {
return res;
}
temp = new int[len];
counter = new int[len];
indexes = new int[len];
for (int i = 0; i < len; i++) {
indexes[i] = i;
}
mergeAndCountSmaller(nums, 0, len - 1);
for (int i = 0; i < len; i++) {
res.add(counter[i]);
}
return res;
}
/**
* 针对数组 nums 指定的区间 [l, r] 进行归并排序,在排序的过程中完成统计任务
*
* @param nums
* @param l
* @param r
*/
private void mergeAndCountSmaller(int[] nums, int l, int r) {
if (l == r) {
// 数组只有一个元素的时候,没有比较,不统计
return;
}
int mid = l + (r - l) / 2;
mergeAndCountSmaller(nums, l, mid);
mergeAndCountSmaller(nums, mid + 1, r);
// 归并排序的优化,同样适用于该问题
// 如果索引数组有序,就没有必要再继续计算了
if (nums[indexes[mid]] > nums[indexes[mid + 1]]) {
mergeOfTwoSortedArrAndCountSmaller(nums, l, mid, r);
}
}
/**
* [l, mid] 是排好序的
* [mid + 1, r] 是排好序的
*
* @param nums
* @param l
* @param mid
* @param r
*/
private void mergeOfTwoSortedArrAndCountSmaller(int[] nums, int l, int mid, int r) {
// 3,4 1,2
for (int i = l; i <= r; i++) {
temp[i] = indexes[i];
}
int i = l;
int j = mid + 1;
// 左边出列的时候,计数
for (int k = l; k <= r; k++) {
if (i > mid) {
indexes[k] = temp[j];
j++;
} else if (j > r) {
indexes[k] = temp[i];
i++;
// 此时 j 用完了,[7,8,9 | 1,2,3]
// 之前的数就和后面的区间长度构成逆序
counter[indexes[k]] += (r - mid);
} else if (nums[temp[i]] <= nums[temp[j]]) {
indexes[k] = temp[i];
i++;
// 此时 [4,5, 6 | 1,2,3 10 12 13]
// mid j
counter[indexes[k]] += (j - mid - 1);
} else {
// nums[indexes[i]] > nums[indexes[j]] 构成逆序
indexes[k] = temp[j];
j++;
}
}
}
public static void main(String[] args) {
int[] nums = new int[]{5, 2, 6, 1};
Solution solution = new Solution();
List<Integer> countSmaller = solution.countSmaller(nums);
System.out.println(countSmaller);
}
}
作者:liweiwei1419
链接:https://leetcode-cn.com/problems/count-of-smaller-numbers-after-self/solution/gui-bing-pai-xu-suo-yin-shu-zu-python-dai-ma-java-/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。