赞
踩
https://leetcode-cn.com/problems/count-of-smaller-numbers-after-self/
如果按照暴力方法的思维,我们可以从后向前遍历,每一次找到当前位置右侧比他小的数有多少个就可以了,那么这样写的时间复杂度为O(N ^ 2),应该是过不了的
vector<int> countSmaller(vector<int>& nums)
{
int len = nums.size();
vector<int> ans(len,0);
for(int i = len-1; i >= 0; i--)
for(int j = i + 1; j < len; j++)
ans[i] += (nums[j] < nums[i]) ? 1 : 0;
return ans;
}
显然,这样做是会超时的
暴力解法的缺点在于,我们每次查找的时候,可以说是把后面的数据都遍历了一次。每次遍历的时间复杂度都是O(N)
,我们是否可以将这个时间复杂度降为O(log N)
。
因为原数组的数据我们不保证一定是有序的,所以我们不能使用二分查找的方法。但是如果我们在从后向前查找的时候,再建立一个数组,在这个新的数组中,他的元素都是有序的(升序)
排列。
这样我们每次只需要在这个数组中找到第一个比他大的数据的位置,那么这个位置的就是结果了,最后再在这个位置插入当前数据就可以了。
vector<int> countSmaller(vector<int>& nums) { int len = nums.size(); vector<int> ans(len,0); deque<int> tmp;//使用双端队列 for(int i = nums.size()-1; i >= 0; i--) { ans[i] = BinaryFind(tmp,nums[i]); tmp.insert(tmp.begin() + ans[i],nums[i]); } return ans; } int BinaryFind(deque<int>& arr,int num) { if(arr.size() == 0) return 0; int le = 0; int ri = arr.size(); while(le < ri) { int mid = (le + ri) / 2; if(arr[mid] < num) le = mid + 1; else if(arr[mid] >= num) ri = mid; } return le; }
这里需要注意,我们在插入的时候,如果使用
O(N)
O(1)
,但是[]重载
插入位置确是O(N)
的时间复杂度所以我们使用deque双端队列,使得插入
和[]重载
的时间复杂度近似于O(1)
二分法的缺点在于,他插入的时候不能完美的做到O(1),为了克服这一问题,我们使用二叉搜素树来解决。
对于二叉搜索树的每一个结点,除了左右子树节点与值之外,还多了一个计数器,用来记录左子树节点的数量,也就是在他的子树中,比自己小的节点的数量。
还是从后向前遍历,当遇到一个节点
nullptr节点
,那么就给nullptr节点
赋值大
。记录比他小的节点数量(计数器 + 1),再次遍历他的右子树小或者等于
。意味着需要在他的左子树进行插入,所以说给当前位置的计数器+1,在遍历他的左子树struct TreeNode { TreeNode* _left; TreeNode* _right; int _val; int _count;//左子树节点个数 TreeNode(int _v = 0) :_val(_v),_left(nullptr),_right(nullptr),_count(0) {} }; //树状 二分 vector<int> countSmaller(vector<int>& nums) { TreeNode* root = nullptr; int len = nums.size(); vector<int> ans(len,0); for(int i = len-1; i >= 0; i--) dfs(root,nums[i],ans,i); return ans; } void dfs(TreeNode*& root,int val,vector<int>& ans,int p) { if(root == nullptr) { root = new TreeNode(val); return; } //比当前节点数值`小或者等于` if(val <= root->_val) { root->_count++; dfs(root->_right,val,ans,p); } else//比当前节点数值`大` { ans[p] += root->_count + 1;//+1表示当前节点本身 dfs(root->_left,val,ans,p); } }
我们可以把最后的问题进行分解,采用归并排序(升序排列)
的思想。其中为了找到每一个数据对于的下标位置,所有我们数组的元素类型为一个pair类型
的结构体,第一个元素是数据,第二个元素是数据对应的下标
而对于归并排序而言,我们需要做手脚的地方有以下四点:(已知,le,mid,ri)
i = mid + 1
。那么右边区间就直接插入对应位置,不需要处理,因为右边区间剩余的元素都是比左边区间大的。i = ri + 1
。那么左边区间就需要插入对于位置,这个时候需要进行处理,因为左边区间的此时的元素都是比右边区间所有元素大的,所以需要加上右边区间的元素个数ri - mid
pr - mid - 1
//归并 vector<int> countSmaller1(vector<int>& nums) { vector<pair<int,int> > copy; for(int i = 0; i < nums.size(); i++) copy.push_back({nums[i],i}); vector<pair<int,int> > tmp = copy; vector<int> ans(nums.size(),0); MergerSort(copy,tmp,0,nums.size()-1,ans); return ans; } void MergerSort(vector<pair<int,int> >& nums,vector<pair<int,int> >& tmp,int le,int ri,vector<int>& ans) { if(le >= ri) return; int mid = (le + ri) / 2; MergerSort(nums,tmp,le,mid,ans); MergerSort(nums,tmp,mid+1,ri,ans); //防止本身就是有序的数组 if(nums[mid].first <= nums[mid+1].first) return; int pl = le; int pr = mid+1; for(int i = le; i <= ri; i++) { if(pl > mid)//左区间元素排列完毕 { tmp[i] = nums[pr++]; } else if(pr > ri)//右区间元素排列完毕 { tmp[i] = nums[pl++]; ans[tmp[i].second] += ri - mid;//此时右区间所有元素都比这个数小 } else if(nums[pl].first <= nums[pr].first)//比较小的元素在左区间 { tmp[i] = nums[pl++]; ans[tmp[i].second] += pr - mid - 1; } else if(nums[pl].first > nums[pr].first)//比较小的元素在右区间 { tmp[i] = nums[pr++]; } } for(int i = le; i <= ri; i++) nums[i] = tmp[i]; }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。