题目:
思路:
(1)最先能想到的当然是遍历。这样使用两个循环,第一个循环遍历每一个数,第二个循环遍历这个数后面的所有数,找有几个是比它小的。这种时间复杂度是O(N^2)
(2)本题使用了分治的思路,复杂度是O(N*logN)
应用合并排序。如果我们对数组进行排序,那么对于某个特定的数据,其后面的逆序数等于在排序过程中需要移动到该数前面的个数。时间复杂度O(nlogn)。
分治的知识见下图
shbi
上面讲的是排序的一个例子。
原本是排序{5,-7,9,8,1,4,-3,10,2,0} 按照mid(mid=begin+end /2)的观念不断的划分子问题
到不能继续划分的{5}{-7} {9} {8} {1}等等的时候,两两合并
看上面的图,本题的主要思路:
假设两个有序(升序)数组进行归并时候,怎么计算count[i]呢(count表示后面有多少个数比nums[i]小)
(1)首先nums[ i_0]=-7,nums[ j_0]=-2,所以-7后面没有比它小的数,count[i_0]不变,i到达i_1的位置, j位置不变
(2)此时nums[i_1]=1,nums[j_0]=-2,所以count[i_1]++, j到达j_1位置(注明一点,开头已经说了有序,所以在一个子数组里面不可能有数比前面的数小)nums[j_1]=1
此时相等怎么办呢,count不用加就好了
……然后后面依次是这么计算count数组的
还有一个问题,就是如何绑定count和nums呢,因为我们最后输出的count是按照nums的数组的数的顺序来的
我们定义数组pos[i] = j,表示第排名i个数据的元素下标是j。i就是排序完之后的数组的顺序,pos[i]记录的是这个数在nums数组的下标,这样最后输出就能按照题目的格式要求了。
java代码
最后返回的result就是count的意思,from to 就是begin end 的意思,m就是mid的意思
public class Solution {
private void sort(int[] nums, int[] smaller, int[] pos, int from, int to) {
if (from >= to) return;
int m = (from + to) / 2;
sort(nums, smaller, pos, from, m);//排序左侧
sort(nums, smaller, pos, m+1, to);//排序右侧
int[] merged = new int[to-from+1];//合并,smaller是最后的count数组
int i=from, j=m+1, k=0, jump = 0;//jump跳了几次,就说明后面有几个数比它小
while (i<=m || j<=to) {//能合并
if (i>m) {
//前面的数组没数了,把后面排好序的数组的数直接加进来
jump ++;//事实证明这句话是废话,不加也ok,加上只是为了说明后面的数跳一个加一下
merged[k++] = pos[j++];//merged[k]=pos[j],k++,j++
} else if (j>to) {
//后面数组没数了
smaller[pos[i]] += jump;//pos[i]是当前数的原始位置
merged[k++] = pos[i++];
} else if (nums[pos[i]] <= nums[pos[j]]) {
smaller[pos[i]] += jump;
merged[k++] = pos[i++];
} else {
jump ++;
merged[k++] = pos[j++];
}
}
//合并完之后,smaller数组已经记录好了原来顺序的数的count,
for(int p=0; p<merged.length; p++) pos[from+p] = merged[p];
}
public List<Integer> countSmaller(int[] nums) {
int[] smaller = new int[nums.length];
int[] pos =new int[nums.length];
for(int i=0; i<pos.length; i++) pos[i] = i;//先记录原先数的下标
sort(nums, smaller, pos, 0, nums.length-1);
List<Integer> result = new ArrayList<>(nums.length);
for(int i=0; i<nums.length; i++) result.add(smaller[i]);
return result;
}
}