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++; //比当前小的记录 +1
return 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];
}
};