当前位置:   article > 正文

Leetcode之计算右侧小于当前元素的个数_列表所有右边比左边小的数个数

列表所有右边比左边小的数个数

题目:

给定一个整数数组 nums,按要求返回一个新数组 counts。数组 counts 有该性质: counts[i] 的值是  nums[i] 右侧小于 nums[i] 的元素的数量。

 

示例:

输入:nums = [5,2,6,1]
输出:[2,1,1,0] 
解释:
5 的右侧有 2 个更小的元素 (2 和 1)
2 的右侧仅有 1 个更小的元素 (1)
6 的右侧有 1 个更小的元素 (1)
1 的右侧有 0 个更小的元素
 

提示:

0 <= nums.length <= 10^5
-10^4 <= nums[i] <= 10^4

 

代码:

方法一——暴力模拟法:

暴力模拟法思路非常简单,就是每次都从末尾找比num[i]小的数并计数,然后放到结果数组即可。
  1. vector<int> countSmaller(vector<int>& nums) {
  2. int n=nums.size();
  3. vector<int>ans(n,0);
  4. int t;
  5. for(int i=n-2;i>=0;i--){
  6. t=0;
  7. for(int j=n-1;j>i;j--){
  8. if(nums[j]<nums[i])
  9. t++;
  10. }
  11. ans[i]=t;
  12. }
  13. return ans;
  14. }

方法二——模拟法+查找:

  1. 我们从暴力模拟法为起点进一步优化,我们看到每次我们都要从末尾遍历相同的元素,实际上我们可以建立一个保持排序的数组sorted_num。
  2. 这个数组代表:在nums[i]之后所有的数,并且已经排好序。
  3. 每次在nums数组出现新的需要判断的数就要插入到这个sorted_num,然后在这个数通过二分查找到下界(可以用STL自带的lower_bound()) 减去sorted_num.begin()就是比nums[i]小的元素个数了。
  1. class Solution {
  2. public:
  3. vector<int> countSmaller(vector<int>& nums) {
  4. vector<int> sorted_num;
  5. /*建立一个保持排序的数组*/
  6. vector<int> res;
  7. /*用于保存结果的数组*/
  8. for(int i=nums.size()-1;i>=0;i--){
  9. auto iter = lower_bound(sorted_num.begin(),sorted_num.end(),nums[i]);
  10. /*通过lower_bound()二分寻找下界,返回一个迭代器(也就是相对于sorted_num的index)*/
  11. int pos = iter-sorted_num.begin();
  12. /*
  13. 通过排序数组的二分查找性质,我们可以知道iter-sorted_num.begin()(可以理解成sorted_num数组的起始位置)就是
  14. 题目需要的比nums[i]小的数字个数
  15. */
  16. sorted_num.insert(iter,nums[i]);
  17. /*这时nums[i]已经使用完了,需要给以后的数字拿来判断
  18. 插入后要保持sorted_num排序,所以nums[i]插入到iter位置*/
  19. res.push_back(pos);
  20. }
  21. reverse(res.begin(),res.end());
  22. /*一路上都是倒序插入的,所以在最后要逆转数组*/
  23. return res;
  24. }
  25. };
  1. class Solution {
  2. public:
  3. vector<int> countSmaller(vector<int>& nums) {
  4. vector<int>t,res(nums.size());      /*初始化t,res*/
  5. for(int i=nums.size()-1;i>=0;i--){
  6. int left=0,right=t.size();        /*下面是一个在t数组里二分查找的过程*/
  7. while (left<right){
  8. int mid=left+(right-left)/2;
  9. if(t[mid]>=nums[i]){
  10. right= mid;
  11. }
  12. else{
  13. left=mid+1;
  14. }
  15. }
  16. res[i]=right;
  17. t.insert(t.begin()+right,nums[i]);
  18. }
  19. return res;
  20. }
  21. };

方法三——记忆化+排序:

还有个很第二种方法比较类似的方法,就是用STL的pari记录:没有排序前每个num[i]对应的下标i,pair<int,int>:nums[i]->i。

记录完之后进行排序。 然后利用已排序的性质进行查找,代码有些复杂且用到了一些位操作的知识,比较难想到,但也是一种非常好的方法。

  1. const int MAXN = 100007;
  2. int cnt[MAXN],n;
  3. /*数组cnt[i]用于记录出现元素nums[i]的个数*/
  4. class Solution {
  5. public:
  6. inline void add(int k)
  7. {
  8. for(; k <= n; k += -k&k) cnt[k] += 1;
  9. }
  10. int sum(int k)
  11. {
  12. int res = 0;
  13. for(; k; k -= -k&k) res += cnt[k];
  14. return res;
  15. }
  16. vector<int> countSmaller(vector<int>& nums) {
  17. n = nums.size();
  18. vector<int> ans(n);
  19. vector<pair<int, int>> A;
  20. /*建立一个从数组内容到未排序前索引的映射A*/
  21. for(int i = 0; i < n; i ++) {
  22. A.push_back({nums[i], i+1});
  23. }
  24. memset(cnt, 0, sizeof(cnt));
  25. sort(A.begin(), A.end());
  26. /*进行排序*/
  27. for(int i = 0; i < n; i ++) {
  28. int id = A[i].second;
  29. int t = sum(n) - sum(id);
  30. ans[id-1] = t;
  31. add(id);
  32. }
  33. return ans;
  34. }
  35. };

方法四——二叉搜索树:

作为一个经常刷leetcode的人来说,刚开始看到这种方法简直是跪着看完的。建立一个二叉搜索树,每个树的结点都有变量val这个结点的值和变量count记录小于该结点的个数。
因为count的最后一个值的结果一定是0,所有先把0放入count数组,并建立一个以val为nums[i-1]的单独结点树。

逆序读nums[i]并建立二叉搜素树,首先排除nums.size()==0的情况,每读取一个nums[i],先去搜索树寻找这个nums[i]对应的答案,
找到答案之后返回给引用参数count_small,再把nums[i]这个值作为新的结点的val插入。 
最后需要将树的结点delete以防内存浪费。
  1. struct BSTNode{
  2. int val;
  3. int count;
  4. BSTNode *left;
  5. BSTNode *right;
  6. BSTNode(int x)
  7. : val(x)
  8. , left(NULL)
  9. , right(NULL)
  10. , count(0)
  11. {}
  12. };
  13. /*建立一个二分查找树,每个树结点有四个值,分别是:
  14. int val;这个结点代表的值val
  15. int count;这个val代表的次数也就是在nums数组种比val小的数的个数
  16. left 左子树指针
  17. right 右子树指针
  18. 一个构造函数,构造函数如上定义
  19. */
  20. void BST_insert(BSTNode *node,BSTNode *insert_node,int &count_small)
  21. {
  22. if(node->val >= insert_node->val)
  23. {
  24. /*插入的结点更小,被比较结点(即node)的count++,然后插入到左子树(如果不为空)*/
  25. node->count++;
  26. if(node->left)
  27. {
  28. BST_insert(node->left,insert_node,count_small);
  29. }
  30. else
  31. {
  32. /*左子树为空,插入结点就作为当前结点的左孩子*/
  33. node->left = insert_node;
  34. }
  35. }
  36. else{
  37. /*插入的结点更大,需要在右子树(如果不为空)继续找*/
  38. count_small += node->count + 1;
  39. if(node->right)
  40. {
  41. BST_insert(node->right,insert_node,count_small);
  42. }
  43. else
  44. {
  45. /*当前右子树为空,插入结点作为当前结点右孩子*/
  46. node->right = insert_node;
  47. }
  48. }
  49. }
  50. /*count_small作为一个引用的参数,在递归寻找子树的时候作为一个“类似全局变量”的存在*/
  51. class Solution {
  52. public:
  53. vector<int> countSmaller(vector<int>& nums) {
  54. int n=nums.size();
  55. /*如果数组为空返回空值*/
  56. if(n==0)return {};
  57. vector<int> count;
  58. count.push_back(0);
  59. /*建立一个二叉搜素树*/
  60. BSTNode* node=new BSTNode(nums[n-1]);
  61. int count_small;
  62. for(int i = 1;i < n;i++)
  63. {
  64. count_small = 0;
  65. BST_insert(node,new BSTNode(nums[n-i-1]),count_small);
  66. count.push_back(count_small);
  67. }
  68. /*最后不要忘记删除树节点*/
  69. delete node;
  70. reverse(count.begin(),count.end());
  71. /*push_back的时候是逆序的,此时只要将count数组reverse即可*/
  72. return count;
  73. }
  74. };

方法五——利用归并排序:

  1. class Solution {
  2. public:
  3. vector<int> countSmaller(vector<int>& nums) {
  4. vector<int>count;//保存结果
  5. vector<pair<int,int> > num;//关联每个数和它的序号
  6. for(int i =0;i<nums.size();++i)
  7. {
  8. count.push_back(0);
  9. num.push_back(make_pair(nums[i],i));//保存每个数和它在原数组中的序号,以免在排序过程中打乱顺序
  10. }
  11. merge_sort(num,count);
  12. return count;
  13. }
  14. //归并排序
  15. void merge_sort(vector<pair<int,int> >& vec, vector<int>& count)
  16. {
  17. if(vec.size()<2)
  18. return;
  19. int mid = vec.size()/2;
  20. vector<pair<int,int> > sub_vec1;
  21. vector<pair<int,int> > sub_vec2;
  22. for(int i =0;i<mid;++i)
  23. sub_vec1.push_back(vec[i]);
  24. for(int i =mid;i< vec.size();++i)
  25. sub_vec2.push_back(vec[i]);
  26. merge_sort(sub_vec1,count);
  27. merge_sort(sub_vec2,count);
  28. vec.clear();
  29. merge(sub_vec1,sub_vec2,vec,count);
  30. }
  31. //合并两数组
  32. void merge(vector<pair<int,int> >& sub_vec1,vector<pair<int,int> >& sub_vec2, vector<pair<int,int> >& vec, vector<int>& count)
  33. {
  34. int i =0;
  35. int j =0;
  36. while(i < sub_vec1.size() && j < sub_vec2.size())
  37. {
  38. if(sub_vec1[i].first <= sub_vec2[j].first )
  39. {
  40. vec.push_back(sub_vec1[i]);
  41. count[sub_vec1[i].second] += j;//这句话和下面注释的地方就是这道题和归并排序的主要不同之处
  42. i++;
  43. }else{
  44. vec.push_back(sub_vec2[j]);
  45. j++;
  46. }
  47. }
  48. for(;i<sub_vec1.size();++i)
  49. {
  50. vec.push_back(sub_vec1[i]);
  51. count[sub_vec1[i].second] += j;// -。-
  52. }
  53. for(;j<sub_vec2.size();++j)
  54. {
  55. vec.push_back(sub_vec2[j]);
  56. }
  57. }
  58. };

方法六——使用树状数组:

这种解法比较少见,速度却是惊人的快。

  树状数组中getSum(index)作用是求原始数组nums中小于或等于当前index的数的和,此处用来统计逆序数,可以把getSum(x)看作是nums中小于x的数的个数的和。

  以题目中的测试数据[5, 2, 6, 1]为例:

  • 初始状态数组c中元素全部置为0
  • 插入5时,大于等于5的所有记录的值+1,此时比5小的数的个数为0;
  • 插入2时,大于等于2的所有记录的值+1,此时比2小的数的个数为0,比5小的数的个数为1;
  • 插入6时,大于等于6的所有记录的值+1,此时比6小的数的个数为2(getSum(6-1)=2),比2小的数的个数为0,比5小的数的个数为1(getSum(5-1)=1);
  • 插入1时,大于等于1的所有记录的值+1,此时比1小的数的个数为0,比6小的数的个数为3(getSum(6-1)=3),比2小的数的个数为1(getSum(2-1)=1),比5小的数的个数为2(getSum(5-1)=2);

  可以看出当把每个数都遍历一遍后,数组arr中分别记录了比每个数小的数的个数之和,题目要求每个数右侧比它小的数的个数,因此用总数减掉每个数左侧比它小的数的个数就可得到。

  从插入元素的过程可以看出,每次插入一个元素时,数组arr中记录的值刚好是插入当前元素之前比该元素小的元素的个数(即当前元素左侧比它小的元素个数),因此只需要在插入一个数之前做一次求和,并用一个数组记录该值即可。

  1. class Solution {
  2. public:
  3. int *arr, n;
  4. int lowbit(int x){
  5. return x&(-x);
  6. }
  7. void update(int pos, int delta){
  8. while (pos <= n){
  9. arr[pos] += delta;
  10. pos += lowbit(pos);
  11. }
  12. }
  13. int getSum(int pos){
  14. int ret = 0;
  15. while (pos){
  16. ret += arr[pos];
  17. pos -= lowbit(pos);
  18. }
  19. return ret;
  20. }
  21. vector<int> countSmaller(vector<int>& nums) {
  22. n = nums.size();
  23. vector<int> ret(n);
  24. if (n == 0){
  25. return ret;
  26. }
  27. int minn = 999999999, maxx = -999999999;
  28. for (int i=0;i<n;++i){
  29. maxx = max(maxx, nums[i]);
  30. minn = min(minn, nums[i]);
  31. }
  32. n = maxx - minn + 2;
  33. arr = new int[n];
  34. memset(arr, 0, sizeof(int)*n);
  35. for (int i=nums.size()-1;i>=0;--i){
  36. ret[i] = getSum(nums[i] - minn);
  37. update(nums[i]-minn+1, 1);
  38. }
  39. return ret;
  40. }
  41. };

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小蓝xlanll/article/detail/87126
推荐阅读
相关标签
  

闽ICP备14008679号