逆序对的计数

逆序对的计数

从自己的博客转载。
leetcode#493题,给定一个数组A,寻找iA[j]的数目。变种为重要逆序对的寻找即寻找A[i]>kA[j]的数目。

题目来源:https://leetcode.com/problems/reverse-pairs/

解题思路

基本的逆序对与重要逆序对思路一致,因此只对逆序对进行讲解。

暴力枚举

从前往后遍历数组,枚举出所有(A[i],A[j]),然后统计符合条件的重要逆序对的数目,很明显所需要的时间复杂度为

O ( n 2 ) O(n^2) O(n2)

分治

分析

先考虑最简单的情况:

如果input的数组长度为n=1,则输出为0,不存在逆序对。会做

n=2的时候,可以分解成两个n=1的情况。因为左右两边都是一个元素自然都不存在逆序对,考虑一个元素在左边,一个在右边的情况可以得出(8,4)一个逆序对的情况,所以输出为1。会做

n=4的时候:同样的可以分解成两个n=2的情况,因为我们已经解决了n=2的问题。这时候左边数组存在一个(8,4)的逆序对,右边数组存在一个(3,2)的逆序对。这时候主要考虑的是交叉的情况,如果左右两个数组都是没有结构的,那么只能用两个指针来挨个遍历左右两边的数组了时间复杂度为 O ( n 2 ) O(n^2) O(n2) 。如果左右两边数组都是有序的情况下,就可以减少很多冗余的比较操作了,当 L i > R j L_i>R_j Li>Rj 时,左边数组位于 L i L_i Li 后面的元素肯定都会大于 R j R_j Rj ,因此就不用在比较 L i L_i Li 后面的元素了,将右边数组指针+1寻找更大的元素来进行比较。当 L i < R j L_iLi<Rj时,需要将左边的指针+1,寻找一个更大的左边元素进行比较。因为左右指针总共的移动次数不超过数组长度,所以这时的复杂度为 O ( n ) O(n) O(n)。以图中的例子来说明,当有4大于3的时候,就不需要比较8是否大于3了。

一个形象的比喻是两个人打牌,一个人L拿的是左边的数组,另一个人R是右边的数组,牌已经从小到大整理好了。如果L最小的牌比R的最小的牌都大,那么L手中所有的牌都比R的最小的牌大。R要找到比L当前牌更大的牌只能向后寻找,如果找不到说明,L所有的牌都比R大,如果找到了是 R j R_j Rj那么L就向后面再找一个新的比 R j R_j Rj的牌大的新牌 L i L_i Li 。规则就是从左到右出牌,牌小的先出,最后谁的牌出完那么游戏结束,结束逆序对的统计。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TtHbu4eW-1574914055448)(https://res.cloudinary.com/bravey/image/upload/v1570434502/blog/reversepairs.jpg)]

归纳

可以看到,在上述分析过程中我们可以将输入为n的数组不断划分为原来的一半直至最后n=1的情况,从n=1的情况再向上合并得到上层问题的答案,也就是归并排序的过程中加上了逆序对的统计,这是一个很典型的分治策略。

Divide 将输入数组A划分为左边A[0, n/2] 与右边A[n/2+1, n-1]两个数组

Conquer 左边的逆序对数目与右边的逆序对数目分别再各自递归的调用函数求解,同时对其排序。

Merge 统计逆序对元素交叉在左右两边的情况,并将两个排好序的子数组合并成一个新的有序数组

复杂度

将规模为n的问题分解成两个两个 n 2 \frac{n}{2} 2n 的子问题问题,同时对两个子问题进行合并的复杂度为O(n),所以有递推公式:

KaTeX parse error: Unknown column alignment: * at position 31: … \begin{array}{*̲*lr**} …
根据主定理有最后的复杂度为 O ( n log ⁡ ( n ) ) O(n\log(n)) O(nlog(n))

代码

/*
https://leetcode.com/problems/reverse-pairs/
 */
#include 
#include 
using namespace std;
class Solution {
	vector<int> tmp_vec;//把tmp_vec设置成共有变量,而不在函数中设置为临时变量可以减少多次对其创建与销毁,提高效率
public:
    int reversePairs(vector<int>& nums) {
    	int size = nums.size();
    	tmp_vec.resize(nums.size());
        return MergeSort(nums, 0, size-1); // 不用全局变量否则,多线程的时候会被修改。
    }

private:
	int MergeSort(vector<int>& vec, int lo, int hi){
		if(lo>=hi) return 0;// base case 递归必备
		int mid = lo + (hi -lo)/2; //防止两个超级大的int相加后造成溢出
		int ans = 0;
		ans += MergeSort(vec, lo, mid); //左边merge的计数
		ans += MergeSort(vec, mid+1, hi); //右边merge的计数
		ans += Merge(vec, lo, hi, mid);// 返回什么? 本次merge的技术 也就是split 的情况
		return ans;
	}

	int Merge(vector<int>& vec, int lo, int hi, int mid){ //采用双指针来一次把左右两边的小值冒泡出来放到合并后的数组中,同时完成对逆序数目的统计
		int p = lo;
		int q = mid + 1;
		int count = 0;//记录逆序数目
		int index = lo;
		while(p<=mid&&q<=hi){
			if((long)vec[p] > (long)vec[q]*3){
				count += mid-p+1;
				q++;
			}else{
				p++;
			}
		}
		//正常的merge操作
		p = lo ;
		q = mid + 1;
		while(p<=mid&&q<=hi){
			if(vec[p]>=vec[q]) tmp_vec[index++]=vec[q++];
			else tmp_vec[index++]=vec[p++];
		}
		while(p<=mid) tmp_vec[index++]=vec[p++];
		while(q<=hi) tmp_vec[index++]=vec[q++];
		for(int i=lo; i<=hi; i++){
			vec[i] = tmp_vec[i];
		}
		return count;
	}    
};

int main(){
	Solution Sol;
	vector<int> vec;
	int n ;
	while(cin>>n){
		//int tmp = n;
		int element;
		while(n--){
			cin>>element;
			vec.push_back(element);
		}
		cout<<Sol.reversePairs(vec)<<endl;
	}
	
}

你可能感兴趣的:(题解)