题目描述
给你一个整数数组 nums **,按要求返回一个新数组 counts **。数组 counts 有该性质: counts[i] 的值是 nums[i] 右侧小于 nums[i] 的元素的数量。
示例 1:
输入: nums = [5,2,6,1]
输出: [2,1,1,0]
解释:
5 的右侧有 2 个更小的元素 (2 和 1)
2 的右侧仅有 1 个更小的元素 (1)
6 的右侧有 1 个更小的元素 (1)
1 的右侧有 0 个更小的元素
示例 2:
输入: nums = [-1]
输出: [0]
示例 3:
输入: nums = [-1,-1]
输出: [0,0]
提示:
1 <= nums.length <= 105-104 <= nums[i] <= 104
解题思路
本题首先可以想到的最简单直接的解题方式是双层循环,内层循环遍历外层循环当前元素后面的元素,如果小于当前元素,则当前元素结果值 +1。这样内层循环结束,就得到了外层循环当前元素的右侧小于它的元素的个数。
但是这样的解题方式时间复杂度是 O(n²) 的,无法达到本题的要求。
那如何才能更高效的求得每一个元素右侧小于它的元素的数量呢?
这里我们可以利用归并排序来解题。
在归并排序回溯合并之前,我们假设右区间元素都小于左区间元素,将左区间元素对应结果值都加上右区间元素数量。
在合并过程中,将左区间元素插入结果数组中时,如果此时右区间有未处理的元素,那它们肯定是大于当前左区间元素的,此时需要将当前左区间元素对应的结果值减去右区间未处理元素的数量。
这样,我们就得到了当前合并过程中右区间中小于左区间的元素的数量。 当归并排序完成后,就得到了每一个元素右侧小于它的元素的数量。
动画演示
代码实现
function countSmaller (nums) {
if(!nums.length) return [];
let objArr = [];//存储值和下标
let resArray = [];//存放次数
for(let i = 0; i < nums.length; i++){
const ele = nums[i];
let obj = {
num: ele,
index: i
}
resArray.push(0);
objArr.push(obj);
}
sliceArray(objArr)
return resArray;
function sliceArray (nums){
let mid = nums.length >> 1;
let left = nums.slice(0,mid);
let right = nums.slice(mid,nums.length);
if(nums.length === 1) return nums;
return merge(sliceArray(left),sliceArray(right));
}
function merge (left, right){
let res = [];
// 声明一个指针,指向后面有序的数组
let j = 0;
while(left.length && right.length){
if(left[0].num > right[0].num){
res.push(right[0]);
right.shift();
j++;
}else{
resArray[left[0].index] += j;//前面有序数组元素出列,数一下后面的有序数组已经出列多少位
res.push(left[0]);
left.shift();
}
}
while(left.length){
resArray[left[0].index] += j;
res.push(left[0]);
left.shift();
}
while(right.length){
res.push(right[0]);
right.shift();
j++;
}
return res;
}
};
至此我们就完成了leetcode-315-计算右侧小于当前元素的个数