赞
踩
如题:
分析思路:
for
循环计算出后面小于当前元素数,但是这样属于两层for
循环,时间复杂度是O(N^2),不可取for
循环完成两两比较那么复杂度是O(N^2),因此这样直接整体统计的方法不可取,我们可以先部分再整体:我们不用一次性计算出当前元素右侧更小的元素个数,可以先统计一部分,最后完成整体统计,因此需要思考有什么方法在遍历数组的时候可以先将一部分元素两两比较,最后完成整体比较的?二路归并排序。
merge
函数将左右两边的子数组合并,注意:我们在merge
合并的时候其实就已经对两个子数组中的元素两两比较了。等到归并排序结束,我们就能够两两比较整个数组。nlogn
,其中logn
代表merge
函数对左右子数组合并的次数,n
代表每次合并操作的复杂度。因此我们可以理解为merge
函数的时间复杂度为O(n)
,我们会调用logn
次merge
函数。
O(n)
,此时如果我们在merge
函数中 额外 进行我们自己需要的遍历,保证我们新增的遍历操作时间复杂度不超过O(n)
,这样加上原本的合并操作,时间复杂度最多也是O(2n)
,忽略常数后时间复杂度不变。merge
函数仅调用logn
次就能两两比较整个数组的特点,在merge函数组增加一个时间复杂度为O(n)
的遍历操作,这样就能够避免嵌套for循环的n²
时间复杂度。HashMap
记录每个元素值到索引的映射,这种方法能简单快速得到每个元素的原始索引,但是只适用于数组中元素不重复的情况value
和index
两个属性,记录每个元素和该元素原始位置int
数组排序改成对这个记录类数组排序,归并排序的时候操作是一样的,只不过需要额外调用一些value
属性罢了。代码如下:
//在归并排序的merge合并两个有序子数组的时候,如果左边大于右边,就让右指针右移,直到碰到左指针的值小于等于右指针的值, // 此时从中间位置到右指针位置之间的所有元素就都是 小于 此时左指针的值,直接累加统计这些元素个数:right-mid-1 class Solution { //定义一个记录类来记录数组中每个元素和原始索引 static class Record{ private int index; private int val; public Record(int index, int val) { this.index = index; this.val = val; } } //由于我们的Record类里面有值,因此可以将对int数组的排序改成对Record数组的排序 private Record[] records; //归并排序的时候需要用到的临时数组 private Record[] temp; //记录每个元素右侧更小元素的个数 private int[] count; public List<Integer> countSmaller(int[] nums) { //一系列初始化 records = new Record[nums.length]; temp = new Record[nums.length]; count = new int[nums.length]; for (int i = 0; i < nums.length; i++) { records[i] = new Record(i,nums[i]); } //进行归并排序 sort(records,0,nums.length-1); //将count数组里记录的加到list集合中 List<Integer> res = new ArrayList<>(); for (int i : count){ res.add(i); } return res; } public void sort(Record[] records,int left,int right){ if(left == right){ return; } //这里不能用(left+right)/2,可能会有益处的风险 int mid = left + (right - left) / 2; sort(records,left,mid); sort(records,mid+1,right); merge(records,left,mid,right); } public void merge(Record[] records,int left,int mid,int right){ //利用System.arraycopy方法将records数组中left到right位置的元素拷贝到temp数组中对应位置, //也可以用for循环一个个赋值,但是该方法效率更高 System.arraycopy(records,left,temp,left,right-left+1); for (int i = left,l=left,r=mid+1; i <= right; i++) { if(l == mid+1){ records[i] = temp[r++]; }else if (r == right+1){//这里r==right+1表示右指针到头了,表示此时右边子数组中所有元素比当前元素小 count[temp[l].index] += r - mid - 1;//也可以直接写right-mid records[i] = temp[l++]; }else if(temp[l].val <= temp[r].val){ //这里条件一定是左边小于等于右边,因为如果只写左边小于右边, // 那么这就意味着当右边等于左边的时候指针也会右移,此时从中间到右边之中的值 // 就不全是小于左边的了,换句话说我们计算右边元素比左边小的数量的时候是利用右指针每次移动都是因为右指针指向的数小于 // 左指针指向的数,这样从mid+1到right-1这些元素就都是小于左指针的元素了,因此右指针和左指针相等的话右指针不应该右移, // 除非计算的是右边元素小于等于当前元素的数量 count[temp[l].index] += r - mid - 1; records[i] = temp[l++]; }else { records[i] = temp[r++]; } } } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。