文章目录
- 1. 题目
- 2. 解题
- 2.1 二叉查找树
- 2.2 二分插入
- 2.3 归并排序
1. 题目
给定一个整数数组 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
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
2. 解题
2.1 二叉查找树
我的博客 二叉查找树
- 每个节点增加一个数据count(记录比这个节点小的元素个数)
- 如果新加入的节点小于当前节点cur,cur->count++
- 从根节点root向下查找,满足条件则累加count值&&路过的比自己小的元素个数
- 特别注意,将原数组逆向(从右向左)插入BST(题目要求找的是右边比我小的)
- 时间复杂度O(nlgn)
- 最坏情况下,BST退化成链表,时间复杂度变成O(n2)
class Node //树的节点
{
public:int val;int count;//小于该节点的个数Node *left, *right;Node(int v): val(v), count(0), left(NULL), right(NULL){}
};
class BST
{Node *root;
public:BST():root(NULL){}int insert(int n){return insert(n, root);}
private:int insert(int n, Node* &cur)//指针的引用,将节点之间连起来{if(!cur){cur = new Node(n);//创建的新节点cur更新至调用处的参数return 0;//自己等于自己,不用加,返回0}else{if(n > cur->val)//我比当前大,+1,还要 +之前记录的比它小的个数,+它右侧的return 1 + cur->count + insert(n,cur->right);if(n < cur->val){ //我比当前小cur->count++; //比当前小的记录 +1return insert(n,cur->left);//去左边子树里查找}else// ==,+比当前小的个数,+右侧里面的return cur->count + insert(n,cur->right);}}
};
class Solution {
public:vector<int> countSmaller(vector<int>& nums) {vector<int> ans(nums.size());BST tree;for(int i = nums.size()-1; i >= 0; --i)ans[i] = tree.insert(nums[i]);return ans;}
};
2.2 二分插入
- 开辟一个空的数组,用于存放已排序的数据
- 将原数组,从右向左,二分插入至新数组,记录插入的位置(前面有多少小于我的)
class Solution {
public:vector<int> countSmaller(vector<int>& nums) {if(nums.empty())return {};vector<int> ans, sorted;int pos;vector<int>::iterator it;for(int i = nums.size()-1; i >= 0; --i){pos = binaryInsert(sorted,nums[i]);it = sorted.begin()+pos;sorted.insert(it,nums[i]);//可能导致vector数据搬移ans.push_back(pos);}reverse(ans.begin(),ans.end());return ans;}int binaryInsert(vector<int>& sorted, int num){int left = 0, right = sorted.size()-1, mid;while(left <= right){mid = left+((right-left)>>1);if(sorted[mid] < num)left = mid+1;else if(sorted[mid] >= num)right = mid-1;}return left;//自己在纸上测试下,只能是left}
};
- 时间复杂度也是O(nlgn)
- 以下结果时间偏长,可能是在vector中间插入数据导致的数据搬移,消耗了时间
2.3 归并排序
参考分治,归并求逆序度
借一张图理解一下
- 需要归并求解,但是归并排序过程中,数据下标变动了,需要建立一个下标数组
- 对下标数组idx进行排序(比较大小的时候用nums数组代入比较)
- 计算逆序数方法(只能取一种,不要同时用)
- 1、当前序数组写入时,计算后序中已经出列的个数(它们均小于刚写入的),
j-(mid+1)
,有后续操作(前序没写完时,继续累加) - 2、当后序数组写入时,计算前序中还有多少没有出队(它们均大于刚写入的),
mid-i+1
,无后序操作(因为,前序出队完毕,剩余0,或者,后序写入完毕)
再借两张图总结下
class Solution {vector<int> ans;//存储结果vector<int> temp;//归并排序临时空间vector<int> idx;//归并排序的对象,排的是---下标,不是数值
public:vector<int> countSmaller(vector<int>& nums) {if(nums.empty())return {};ans.resize(nums.size());temp.resize(nums.size());idx.resize(nums.size());for(int i = 0; i < nums.size(); ++i){idx[i] = i;ans[i] = 0;}mergeSort(nums,0,nums.size()-1);return ans;}void mergeSort(vector<int> &nums, int l, int r){if(l == r)return;int mid = l+((r-l)>>1);mergeSort(nums,l,mid);mergeSort(nums,mid+1,r);merge(nums,l,mid,r);}void merge(vector<int>& nums, int l, int mid, int r){int i = l, j = mid+1, k = l;while(i <= mid && j <= r){if(nums[idx[i]] <= nums[idx[j]]){ans[idx[i]] += j-(mid+1);temp[k++] = idx[i++];}else{temp[k++] = idx[j++];}}while(i <= mid){ans[idx[i]] += j-(mid+1);//or ans[idx[i]] += r-mid;temp[k++] = idx[i++];}while(j <= r){temp[k++] = idx[j++];}for(i = l; i <= r; ++i)idx[i] = temp[i];}
};