题目:
给你一个整数数组 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 个更小的元素
方法一:暴力循环(超时)
所谓计算counts数组,即求每一个数字右侧比他小的个数。首先我们想到的就是暴力循环。下面是暴力循环的代码。虽然暴力循环的代码较为简洁但时间复杂度为O(n²),根据数据长度会超时。
class Solution {
public:
vector<int> countSmaller(vector<int>& nums) {
vector<int>ans(nums.size());
for(int i = 0;i < nums.size();i++){//暴力循环
for(int j = i + 1;j < nums.size();j++){
if(nums[j] < nums[i]){
ans[i]++;
}
}
}
return ans;
}
};
方法二:利用归并排序进行计算
首先,我们需要了解以下什么是归并排序,我们先看下面代码帮助理解:
#include<iostream>
#include <vector>
using namespace std;
#define C_MAX 10010
class Solution {
public:
int tem[C_MAX];
void mergesort(vector<int>&nums,int left,int right){
int mid = left + (right - left) / 2;
mergesort(nums,left,mid);
mergesort(nums,mid + 1,right);//进行递归
int i = 0;
int count1 = left;
int count2 = mid + 1;
while(count1 <= mid && count2 <= right){//对两边进行判断大小然后入队
if(nums[count1] <= nums[count2]){
tem[i++] = nums[count1++];
}
else{
tem[i++] = nums[count2++];
}
}
while(count1 <= mid){
tem[i++] = nums[count1++];
}
while(count2 <= right){
tem[i++] = nums[count2++];
}
}
};
以上代码就是归并排序的过程,我们也可以通过下面的图来加深理解(图来自力扣-flix)
在归并排序的时候,利用上图,我们可以看出逆序对的个数,把一个数组分成两个部分分别进行递归排序,在合成的时候,我们需要按照其大小进行排序合成,但图中画圈的地方(按照两个部分的前后,若前面部分有数字大于后面数字即存在逆序对数,例如,65数组分为l1[6],l2[5],以为本身l1就在l2的前面,并且6>5,即存在一个逆序对数),可以明显的计算出逆序对数。
但这样,我们只能计算逆序对数,无法记录每个数的逆序对数。我们可以通过使用一个索引数组跟着一起排序,就可以知道该数字的在原始数组的下标。便可以记录该数字的逆序对数。
class Solution {
public:
int tem1[100100]; //数组1用来临时记录数组
int tem2[100100]; //数组2用来临时记录索引
void mergesort(vector<int>&nums,vector<int>&index,vector<int>&ans,int l,int r){
if(l >= r){
return;
}
int mid = l + (r - l) / 2;
int count1 = l;
int count2 = mid + 1;
mergesort(nums,index,ans,l,mid);
mergesort(nums,index,ans,mid + 1,r); //分别递归进行归并排序。
int i = 0;
while(count1 <= mid && count2 <= r){
if(nums[count1] <= nums[count2]){
ans[index[count1]] += count2 - mid - 1; //这里的计算是用下面的图来解释
tem1[i] = nums[count1];
tem2[i++] = index[count1++]; //始终保持下标和原数组一致
}
else{
tem1[i] = nums[count2];
tem2[i++] = index[count2++];
}
}
while(count1 <= mid){
ans[index[count1]] += count2 - mid - 1;
tem1[i] = nums[count1];
tem2[i++] = index[count1++];
}
while(count2 <= r){
tem1[i] = nums[count2];
tem2[i++] = index[count2++];
}
for(int i = l;i <= r;i++){
index[i] = tem2[i - l];
nums[i] = tem1[i - l]; //上面与归并排序基本类似
}
}
vector<int> countSmaller(vector<int>& nums) {
int n = nums.size();
vector<int>ans(n);
vector<int>index(n);
for(int i = 0;i < n;i++){
index[i] = i;
}
mergesort(nums,index,ans,0,n - 1);
return ans;
}
};
计算逆序对数的技巧:
对于1的时候, 右边没有比小的故跳过。3的时候,右边的指针指到4,所以4前面的个数就是比3小的个数,3的逆序对数为1。5的时候,右边指针指到7,故5的逆序对数为2,6亦是如此。(因为我们使用index数组,保持数字仍然与原数组下标一致,故可以直接计算)。