问题描述
You are given an integer array nums and you have to return a new counts array. The counts array has the property where counts[i] is the number of smaller elements to the right of nums[i].
Example:
Given nums = [5, 2, 6, 1]
To the right of 5 there are 2 smaller elements (2 and 1).
To the right of 2 there is only 1 smaller element (1).
To the right of 6 there is 1 smaller element (1).
To the right of 1 there is 0 smaller element.
Return the array [2, 1, 1, 0].
最开始由编程之美的光影切割问题中求逆序対找到这道题,网上有很多非常巧妙的解法,自己实现了两种。
方法一:改写归并
private static void reverse(int[] nums, int[] smaller, int[] pos, int start, int end){
if(start>=end){
return ;
}
int mid=start+(end-start)/2;
reverse(nums, smaller, pos, start, mid);
reverse(nums, smaller, pos, mid+1, end);
int[] temp=new int[end-start+1];
int i=start, j=mid+1, k=0;
int count=0;
while(i<=mid||j<=end){
if(i>mid){
temp[k++]=pos[j++];
}else if(j>end){
smaller[pos[i]]+=count;
temp[k++]=pos[i++];
}else if(nums[pos[i]]<=nums[pos[j]]){
smaller[pos[i]]+=count;
temp[k++]=pos[i++];
}else{
count++;
temp[k++]=pos[j++];
}
}
for(i=0; i<k; i++){
pos[start+i]=temp[i];
}
}
public static List<Integer> countSmaller(int[] nums) {
List<Integer> re=new ArrayList<Integer>();
if(nums==null||nums.length==0){
return re;
}
int[] smaller=new int[nums.length];
int[] pos=new int[nums.length];
for(int i=0; i<pos.length; i++){
pos[i]=i;
}
reverse(nums, smaller, pos, 0, nums.length-1);
for(int i=0; i<smaller.length; i++){
re.add(smaller[i]);
}
return re;
}
因为要保存原来的顺序,所以没有直接改写数组,而是用一个pos数组来存储对应的位置。参考博客http://blog.csdn.net/jmspan/article/details/51219203
方法二:树状数组
学习了树状数组和线段树这两种数据结构。下面是用树状数组实现的代码
private static int[] tree;
//方法1、通过二分查找
public static List<Integer> countSmaller(int[] nums) {
int[] temp=nums.clone();
Arrays.sort(temp);
for(int i=0; i<nums.length; i++){
nums[i]=Arrays.binarySearch(temp, nums[i]);
}
tree=new int[nums.length];
Integer[] re=new Integer[nums.length];
for(int i=nums.length-1; i>=0; i--){
re[i]=sumRange(nums[i]);
add(nums[i]+1, 1);
}
return Arrays.asList(re);
}
private static void add(int index, int val){
while(index<tree.length){
tree[index]+=val;
index+=lowBit(index);
}
}
private static int sumRange(int index){
int re=0;
while(index>0){
re+=tree[index];
index-=lowBit(index);
}
return re;
}
private static int lowBit(int index){
return index&(-index);
}
参考博客http://www.cnblogs.com/bigchencheng/p/5151382.html
当然树状数组还有改进的空间,上面把原数组通过二分查找改成0-nums.length的新数组,这样虽然改变了数值,但是相对大小是不变的,因此对答案没有影响。在leetcode讨论区有人给出了如下的改进。
public static List<Integer> countSmaller2(int[] nums) {
pre(nums);
tree=new int[11000];
Integer[] re=new Integer[nums.length];
for(int i=nums.length-1; i>=0; i--){
re[i]=sumRange(nums[i]);
add(nums[i]+1, 1);
}
return Arrays.asList(re);
}
private static void pre(int[] nums){
int min=Integer.MAX_VALUE;
for(int num: nums){
min=Math.min(min, num);
}
if(min<0){
min=-min+1;
for(int i=0; i<nums.length; i++){
nums[i]+=min;
}
}
}
这种方法虽然空间上存在一定限制,但是避免了用二分求改变原数组,因此在这道题上,时间是最短的。
参考地址https://discuss.leetcode.com/topic/31154/complicated-segmentree-solution-hope-to-find-a-better-one
本题还可以利用二叉搜索树、线段树的方法来解,详细说明参看博客http://www.cnblogs.com/yrbbest/p/5068550.html