leetcode 315 计算右侧小于当前元素的个数
首先贴下自己的做法,虽然超时了。通过从后向前的遍历,并将当前元素插入右边有序元素中,则从该元素开始的右边元素都是有序的。并且遍历过程中能够通过二分查找获得的序号来计算小于自己的元素个数。贴代码
1 class Solution { 2 public: 3 vector<int> countSmaller(vector<int>& nums) 4 { 5 int n = nums.size(); 6 vector<int> res(n); 7 res[n-1] = 0; 8 for(int i = n-2 ; i >= 0 ; i--) 9 { 10 //二分查找 11 int tempInt = nums[i]; 12 int left = i+1; 13 int right = n-1; 14 //二分查找 15 while(left<=right) 16 { 17 int mid = (left+right)/2; 18 if(nums[mid]>=tempInt) 19 right = mid-1; 20 else if(nums[mid]<tempInt) 21 left = mid+1; 22 } 23 //待排序值大于所有 24 if(left == n) 25 { 26 for(int j = i ; j <= n-2 ; j++) 27 nums[j] = nums[j+1]; 28 nums[n-1] = tempInt; 29 res[i] = n-1-i; 30 } 31 else 32 { 33 for(int j = i ; j <= right-1 ; j++) 34 nums[j] = nums[j+1]; 35 nums[right] = tempInt; 36 res[i] = right-i; 37 } 38 } 39 return res; 40 } 41 };
第二种方法是使用了树状数组的数据结构。可以用桶的方式来记录某一个数在数组中的出现频率。同样进行从后向前的遍历,对于遍历到的某一个数而言,其右边的小于当前数的个数就是桶中当前数前方所有元素的和。而这个数组求和的操作则能够使用树状数组的结构来实现。另外的一个技巧是使用离散化优化空间,其实就是将源数组中的值域连续的映射到一个连续的整数空间。原数组中每个数映射为新数组中该数组对应的完成映射之后,可通过二分查找的方式获得索引,能一定程度上减少空间消耗。代码中最复杂应当是树状数组的实现,贴代码
1 class Solution { 2 private: 3 vector<int> a; 4 vector<int> c; 5 void Init(int length) 6 { 7 c.resize(length,0); 8 } 9 int LowBit(int x) 10 { 11 return x&(-x); 12 } 13 void Update(int pos) 14 { 15 while(pos<c.size()) 16 { 17 c[pos] += 1; 18 pos += LowBit(pos); 19 } 20 } 21 int Query(int pos) 22 { 23 int ret = 0; 24 while(pos>0) 25 { 26 ret += c[pos]; 27 pos -= LowBit(pos); 28 } 29 return ret; 30 } 31 32 void Discretization(vector<int>& nums) 33 { 34 a.assign(nums.begin(),nums.end()); 35 sort(a.begin(),a.end()); 36 a.erase(unique(a.begin(),a.end()),a.end()); 37 } 38 39 int getId(int x) 40 { 41 return lower_bound(a.begin(),a.end(),x)-a.begin()+1; 42 } 43 public: 44 vector<int> countSmaller(vector<int>& nums) 45 { 46 vector<int> resultList; 47 Discretization(nums); 48 Init(nums.size() + 5); 49 for(int i = (int)nums.size()-1;i>=0;--i) 50 { 51 int id =getId(nums[i]); 52 resultList.push_back(Query(id-1)); 53 Update(id); 54 } 55 reverse(resultList.begin(),resultList.end()); 56 return resultList; 57 } 58 };
还有一种归并排序的方法,很巧妙。归并排序分为两个步骤,首先分隔开,分成独自一个的个体之后就进行合并。而该题就利用了合并过程,来实现右边的小于该数的计算。假设对两组有序的数进行归并,假设在某一时刻,第一组中遍历到了i,并且在这时第二组遍历到了j,并且i<j,在排序过程中会将i放入目标数组中,而这一步骤另外一个隐含的意义就是,第二组数中j之前的所有数都小于i,不然也轮不到j,i就会被放入目标数组。于是将第二组数中小于j的数的个数加之对应的结果数组中。而在递归的过程中,某一数右边的所有数都会经过这个判断,递归完成时,也就得到了最后的答案。另外一个要点就是建立原始数组中的数与其在初始数组中的索引的对应关系。在代码中就是index这个数组。因为归并排序会改变元素的位置,所以,index数组的含义为,index该数组的某一索引以及该索引在数组中的对应值分别为某一数据在修改过的数组中的索引和其原始索引。通过维护这个数组,可以进行最终结果的累加。另一个要点是,merge函数以及mergeSort函数的数组形参要写成引用,不然排序就没有意义。贴代码
1 class Solution { 2 private: 3 vector<int> index; 4 vector<int> temp; 5 vector<int> tempIndex; 6 vector<int> res; 7 public: 8 vector<int> countSmaller(vector<int>& nums) 9 { 10 int n = nums.size(); 11 index.resize(n,0); 12 for(int i = 0 ; i < n ; i++) 13 index[i] = i; 14 temp.resize(n,0); 15 tempIndex.resize(n,0); 16 res.resize(n,0); 17 mergeSort(nums,0,n-1); 18 return res; 19 } 20 void mergeSort(vector<int>& a,int l,int r) 21 { 22 if(l>=r) 23 return; 24 int mid = (l+r)/2; 25 mergeSort(a,l,mid); 26 mergeSort(a,mid+1,r); 27 merge(a,l,r,mid); 28 } 29 void merge(vector<int>& a,int l,int r,int mid) 30 { 31 int i = l; 32 int j = mid+1; 33 int p = l; 34 while(i<=mid && j<=r) 35 { 36 if(a[i]<=a[j]) 37 { 38 temp[p] = a[i]; 39 tempIndex[p] = index[i]; 40 res[index[i]] += (j-mid-1); 41 i++; 42 p++; 43 } 44 else 45 { 46 temp[p] = a[j]; 47 tempIndex[p] = index[j]; 48 j++; 49 p++; 50 } 51 } 52 while(i<=mid) 53 { 54 temp[p] = a[i]; 55 tempIndex[p] = index[i]; 56 res[index[i]] += (j-mid-1); 57 i++; 58 p++; 59 } 60 while(j<=r) 61 { 62 temp[p] = a[j]; 63 tempIndex[p] = index[j]; 64 j++; 65 p++; 66 } 67 for(int k = l ; k <= r ; k++) 68 { 69 a[k] = temp[k]; 70 index[k] = tempIndex[k]; 71 } 72 } 73 };

浙公网安备 33010602011771号