这个题需要用到:
①逆序对的解决思路。
②归并的分治策略
③特殊的排序,不动原数组nums,而是另开辟一个保存下标的数组index。并且排序也是在这个index数组上进行的,原数组只是用来比较。
题目:
给定一个整数数组 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 个更小的元素.
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/count-of-smaller-numbers-after-self
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
思路:这个题目很难,主要是需要使用到逆序对这种策略。在刚看到题目的时候有点懵,主要还是接触的太少了。后来看题目提示,根据提示现学了有关逆序对策略以及树状数组的相关知识,奈何树状数组太麻烦了233333,最后选择了使用逆序对的策略来解决。
首先介绍逆序对是个什么问题:
如果给定一个数组序列,nums[0.....n]。对于 o<=i,j < n,如果i<j,而且nums[i] > nums[j],则称nums[i]与nums[j]构成一对逆序对。
如给定: [2, 1, 4, 6, -1]。此时逆序对有:2与1,2与-1,1与-1,4与-1,6与-1。总共五对。
接下来介绍策略:
因为在更新当前位置逆序对数目时,由于使用分治策略,所以“原数组”分为前半段有序序列和后半段有序序列,此时只需要使用两个指针,使用归并排序的想法,只是在排序的过程中增加一行更新当前位置的大于的右边元素的数目。
如:[5, 2, 6, 1]
划分为 5,2 | 6,1
左指针从5处开始,右指针从6处开始,
如果nums[left] <= nums[right],则和归并排序无二致。
如果nums[left] > nums[right],又因为右半段是已经有序的,所以right后面的数也肯定小于nums[left],
假设右半段序列的右端点为right_len,则此时值为5处的数目更新为 right_len - right。
Note:最好绘图。绘图出来特别好理解。。。
对应的更新2处的数目。
说的不怎么清楚,大家最好画图去理解,还是很好理解的。
主要理解:
①排序时不动原数组,而是动下标索引数组。
②因为在更新的时候,左右两端子序列已经是有序的了,所以可以直接根据right指针的下标位置与整个右半段子序列的长度的关系来更新当前位置处的大于右边的元素数目。
实现代码:
JAVA版:
package leetcode;
/*
USER:LQY
DATE:2020/7/11
TIME:8:25
*/
import java.util.ArrayList;
import java.util.List;
public class leetcode_315_important {
public List<Integer>countSmaller(int []nums){
List<Integer> ans = new ArrayList<>();
int len = nums.length;
if(len == 0) return ans;
count = new int[len];
temp = new int[len];
index = new int[len];
//初始化index数组
for(int i = 0;i < len;i++)
index[i] = i;
if(len%2 == 0){
//偶数
suber(nums, 0, len-1);
}else{
//奇数长度最后一个另外处理
suber(nums, 0, len-2);
//再处理最后一个
int last = nums[len-1];
for(int i = 0;i < len-1;i++){
if(nums[i] > last){
count[i]++;
}
}
}
for(int i: count){
System.out.print(i+"\t");
ans.add(i);
}
System.out.println();
return ans;
}
private int []count;
private int []index; //存储原始数组的下标。排序的时候交换下标而原数组不变
private int []temp; //和一般的归并排序一样,需要一个额外的数组空间
public void suber(int []nums, int left, int high){
if(left >= high) return;
int mid = (left + high) >> 1; //用移位计算比较好
suber(nums, left, mid);
suber(nums, mid+1, high);
mergeAndCount(nums, left, mid+1, high);
}
public void mergeAndCount(int []nums, int left, int mid, int right){
//这样会超时
// for(int i = left;i < mid;i++){
// int lval = nums[i];
// for(int j = mid;j <= right;j++){
// if(lval > nums[j]) count[i]++;
// }
// }
int i = left;
int j = mid;
int newi = left;
while(i<mid && j<=right){
if(nums[index[i]] <= nums[index[j]]){
temp[newi++] = index[j++];
}else{
count[index[i]] += right - j + 1; //这个地方是与传统归并排序唯一不同的地方。也是用来统计的关键
temp[newi++] = index[i++];
}
}
while(i < mid){
temp[newi++] = index[i++];
}
while(j <= right){
temp[newi++] = index[j++];
}
//此时,从left->right的index对应的temp数组已经排好序。
// 这一趟排完序后将temp中的部分有序数列复制到index中
for(int k = left;k <= right;k++){
index[k] = temp[k];
}
}
public static void main(String[] args) {
int[] nums = new int[]{5183, 2271, 3067, 539, 8939, 2999, 9264, 737, 3974};
leetcode_315_important solution = new leetcode_315_important();
List<Integer> countSmaller = solution.countSmaller(nums);
System.out.println(countSmaller);
}
}
Python版:
class solution_315:
def countSmaller(self, nums: list[int]) ->list[int]:
lens = len(nums)
ans = [0 for _ in range(lens)]
if(lens == 0):
return ans
count = [None for _ in range(lens)]
temp = [None for _ in range(lens)]
index = [i for i in range(0, lens)]
#初始化index
for i in range(lens):
index[i] = i
self.suber(nums, index, temp, count, 0, len-1)
for i in count:
ans.add(i)
return ans
def suber(self, nums, index, temp, count, left, right):
if(left >= right):
return
mid = (left + right) >> 1
self.suber(nums, index, temp, count, left, mid)
self.suber(nums, index, temp, count, mid+1, right)
self.mergeAndCount(nums, index, temp, count, left, mid+1, right)
#从大到小排序
def mergeAndCount(self, nums, index, temp, count, left, mid, right):
j = mid
newi = i = left
while(i<mid and j<=right):
if(nums[index[i]] <= nums[index[j]]):
temp[newi] = index[j]
newi += 1
j += 1
else:
count[index[i]] += right - j + 1
temp[newi] = index[i]
newi += 1
i += 1
while(i < mid):
temp[newi] = index[i]
newi += 1
i += 1
while(j <= right):
temp[newi] = index[j]
newi += 1
j += 1
for k in range(left, right+1):
index[k] = temp[k]