引发我对逆序对这个问题思考的源自这道题:315. Count of Smaller Numbers After Self这道题要找出某个元素之后出现的比这个元素小的元素个数:ex. Given[5, 2, 6, 1], return [2, 1, 1, 0]
这道题只是求逆序对个数的另外一个变形而已,所以这篇就着重说一下数组中逆序对个数的四种求法。
归并方法
以前我以为求一个序列逆序对个数的解法就是归并排序的改版。归并解法:
因为要求每个元素的逆序对个数,所以一下解法的相当一部分花在来找原数组的对应关系上面。
vector<int> res;
void mergesort(vector<II>& nums,int left,int right){
if(left>= right)
return;
int mid = (left+right)/2;
mergesort(nums,left,mid);
mergesort(nums,mid+1,right);
vector<II> leftnums(mid-left+1),rightnums(right-mid);
for(int i = left;i<=mid;i++)
leftnums[i-left] = nums[i];
for(int i = mid+1;i<=right;i++)
rightnums[i-mid-1] = nums[i];
leftnums.push_back(II(INT_MAX,-1));
rightnums.push_back(II(INT_MAX,-1));
int i = 0,j=0,k = left;
int mark = 0;
while(i<leftnums.size()-1||j<rightnums.size()-1){
if(leftnums[i]<=rightnums[j]){
res[leftnums[i].second] += mark;
nums[k++] = leftnums[i++];
}else{
mark++;
nums[k++] = rightnums[j++];
}
}
return;
}
vector<int> countSmaller(vector<int>& nums) {
res = vector<int>(nums.size(),0);
vector<II> new_nums(nums.size());
for(int i = 0;i<nums.size();i++)
new_nums[i] = II(nums[i],i);
mergesort(new_nums,0,nums.size()-1);
return res;
}
BST(二叉搜索树)
先说一个额外的变形题型:
《程序员面试金典》上面的题目:P77 11.8题:读取一串整数流,要求尽可能短的时间内,返回已读取的数字流中,比x小的元素个数。设计
void add(int x)和int getRankOfNumber(int x)两个函数接口。
如果不是一个数字流的话,就直接将这个数组排序,然后每个元素排名出现的位置,就是整个数组中比其小的元素个数。
但是如果处在流中,则每次排序花费太大,此时需要一个数组结构能够完成这个工作。所以此时就想到二叉树是一个非常好的数据结构。【一般需要用到数据结构的问题中,就想一下:堆,二叉树,栈,队列这四个方面想就八九不离十了】
所以弄一个BST,每个节点中额外记录一下,这个节点左子树的元素个数LeftNum。
getRankOfNumber(int x)函数
就沿着BST数去查找这个元素所在的数节点,在查找的途中,如果要往左子树查找的话,就不做改变,如果要往右子树查找的话,即当前节点和其左子树所有节点都比它小,所以res
+= LeftNum+1
add(int x)函数
就是一个经典的BST插入,不过当其要插入当前节点的左子树节点中时,就将当前节点的LeftNum值加一即可。
再回到这道题本身,其实当时第一眼看到这道题的时候,我并没有往逆序对这种情况上去想,而是直接就参照上面那题的思路用二叉树去做了,二叉树的解法就是,从右往左一次扫描这个二叉树,进行建树,在插入节点的时候,就直接找出当前情况下它的rank值存到数组中即可。
我的代码如下:
struct TreeNode {
int val;
int count;
TreeNode *left;
TreeNode *right;
TreeNode(int x,int c):val(x),count(c),left(NULL), right(NULL) {}
};
vector<int> countSmaller(vector<