题目描述:
给定一个整数数组 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 个更小的元素.
原题链接
思路:
题目的问题很简单,最容易想到的是暴力法,两个for循环依次判断即可,这样做的时间复杂度为O(n^2),数据量很大是会超时,不能AC。
仔细思考发现这个题和求数组的逆数对的题很相似,可以采用二路归并的思想解决,对于二路归并思想有些模糊的同学可以参考参考下面这两篇文章,
深入理解归并排序
二路归并实战
笔者认为这两篇是写的很不错的文章。
AC代码如下(有详细注释):
import java.util.Arrays;
public class Main {
static int[] index;//存元素的下标
static int[] temp;//归并排序需要用到的临时数组
static int[] count;//对每个元素的统计
public static void main(String[] args) throws IOException {
int[] a = {5,2,6,1};
index = new int[4];
for(int i = 0;i<4;i++) index[i] = i;
temp = new int[4];
count = new int[4];
merge(a,0,3);
System.out.println(Arrays.toString(count));//结果为[2,1,1,0]
}
//注意:该方法名义上是要让nums数组升序,但实际交换的是index数组,这样便于统计每个元素的右边小于它的个数
public static void merge(int[] nums,int left,int right){
if(left == right) return;//一个元素肯定有序
int mid = (left+right) >> 1;
merge(nums,left,mid);//归并左子数组
merge(nums,mid+1,right);//归并右子数组
//要归并的子数组已然有序,就不需要统计了,直接返回
if(nums[index[mid]]<=nums[index[mid+1]]) return;
int i = mid,j = right,idx = right;
while (i>=left && j>mid){
//统计
if (nums[index[i]]>nums[index[j]]){//形如5,2,此时i值为0指向5,j值为1指向2
count[index[i]] += j-mid;//5在原始数组nums的下标为0
temp[idx--] = index[i--];
}else temp[idx--] = index[j--];
}
while (i>=left) temp[idx--] = index[i--];
while (j>mid) temp[idx--] = index[j--];
//交换index数组,交换的是下标,原始数组nums不变,通过下标可以映射到数组nums的元素
//虽然下标不是有序的,但通过下标映射到数组nums的值便是有序的
for(i+=1;i<=right;i++){
index[i] = temp[i];
}
}
}