题目描述
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。
示例 1:
输入: [7,5,6,4]
输出: 5
限制:
0 <= 数组长度 <= 50000
归并排序
leetcode官方视频讲解很不错!
大概思路:
- 归并排序:递归的将数组拆分为两个分组,直到拆分至分组个数为1(因为1个数的时候默认是有序的),然后开始回升,不断地去有序合并被拆分的两个分组(此时的两个分组已经排好序了)。
- 与普通归并排序不同的是,在两个分组合并的时候需要去计算逆序对。
- 怎么计算?具体来说,两个分组已经都是各自有序的,合并的时候是先借用辅助数组来存储排序前的两个分组,然后双指针比较,谁小谁先放入原来数组中。如果是右边那个分组的指针所指的数更小,则放入原来数组,且说明左边分组中所有剩下的数都比这个数大,那就计入逆序对数量中。
- 时间复杂度:同归并排序O(NlogN)。递归分解数据,需要递归logN次,每次都需要对N个数据扫描一次,最好最坏平均都一样,所以O(NlogN)
- 空间复杂度:同归并排序O(N)。用到了辅助空间,每次合并之前都会把未合并的顺序存到辅助空间,然后读比较temp数组,结果写存到原来数组里。与其每次合并的时候去分配空间,不如一次性开辟好,更节省空间
- 归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。
- 算法描述:
- 把长度为n的输入序列分成两个长度为n/2的子序列;
- 对这两个子序列分别采用归并排序;
- 将两个排序好的子序列合并成一个最终的排序序列。
class Solution {
public:
/*
归并排序:递归的将数组拆分为两个分组,直到拆分至分组个数为1(因为1个数的时候默认是有序的),然后开始回升,不断地去有序合并被拆分的两个分组。与普通归并排序不同的是,在两个分组合并的时候需要去计算逆序对。
怎么计算?具体来说,两个分组已经都是各自有序的,合并的时候是先借用辅助数组来存储排序前的两个分组,然后双指针比较,谁小谁先放入原来数组中。如果是右边那个分组的指针所指的数更小,则放入原来数组,且说明左边分组中所有剩下的数都比这个数大,那就计入逆序对数量中。
*/
//函数:基于双指针方法,将两个分组[left...mid]和[mid+1...right]有序合并,注意两个分组内部本身已经排好序
int merge(vector<int>& nums, int left, int mid, int right, vector<int>& temp){
int paircount = 0;
//每次合并之前都会把未合并的顺序存到temp,然后比较temp数组,结果存到nums里
for(int k = left; k <= right; k++ ){
temp[k] = nums[k];
}
//i、j是两个分组的双指针,k指向辅助数组的位置
int i = left, j = mid + 1;
for( int k = left; k <= right; k++ ){
if( i > mid ){ //当第一个分组已经遍历完,那么直接放第二个分组的内容
nums[k] = temp[j];
j++;
}else if( j > right ){ //当第二个分组已经遍历完,那么直接放第一个分组的内容
nums[k] = temp[i];
i++;
}else if( temp[i] <= temp[j] ){ //谁小就放谁。为了保证排序的稳定性,等于号放左边的分组
nums[k] = temp[i];
i++;
}else{ //当右边分组指针所指的数更小
nums[k] = temp[j];
j++;
paircount += mid - i + 1; //左边分组中所有剩下的数都比这个数小,那就计入逆序对数量中
}
}
return paircount;
}
//函数:对数组nums[left...right]进行归并排序
int mergeSort(vector<int>& nums, int left, int right, vector<int>& temp){
//拆分的终止条件:只有一个数的时候,默认已排好序
if( left == right ){
return 0;
}
//否则,就要递归不停的拆分为两个分组[left...mid]和[mid+1...right],然后再有序合并
// int mid = ( left + right ) / 2; //left + right有可能超出最大整数范围
int mid = left + ( right - left ) / 2;
int leftpairs = mergeSort(nums, left, mid, temp); //返回左分组内部的逆序对
int rightpairs = mergeSort(nums, mid+1, right, temp); //返回右分组内部的逆序对
//拆分至分组个数为1的时候会开始回升(有点类似于树的后序遍历)
int crosspairs = merge(nums, left, mid, right, temp); //返回两个分组之间的逆序对,在这个过程中注意计算逆序对
return leftpairs + rightpairs + crosspairs;
}
//函数:计算数组nums中的逆序对数量
int reversePairs(vector<int>& nums) {
int size = nums.size();
if( size <= 1 ){
return 0;
}
//考虑到不建议直接在输入数组上做排序,所以另外开辟一个空间,存储排序结果
// vector<int> copynums(size);
// for(int i=0; i<size; i++)
// copynums[i] = nums[i];
//另外,每一次合并的时候需要用到辅助数组,用来存储并遍历排序前的两个分组。与其每次都分配空间,比如一次性开辟好,更节省空间
vector<int> temp(size);
return mergeSort(nums, 0, size-1, temp); //归并排序
}
};