剑指offer面试题51(java版):数组中的逆序对
题目描述
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P%1000000007
输入描述:
题目保证输入的数组中没有的相同的数字
数据范围:
对于%50的数据,size<=10^4
对于%75的数据,size<=10^5
对于%100的数据,size<=2*10^5
示例1
输入
1,2,3,4,5,6,7,0
输出
7
第四次做; 进行归并排序, 必须是降序排序, 假如还是升序, 那么合并{4,5,6}和{1,2,3}时, p1指向4, p2指向1, 第一次循环res=1,p2++; 第二次循环res=3,p2++; 第三次循环res=6,p2++; 但实际上4能构成的逆序对只有3个
class Solution {
public int reversePairs(int[] nums) {
int n = nums.length;
if(n<2){
return 0;
}
return mergeSort(nums, 0, n-1);
}
private int mergeSort(int[] nums, int left, int right){
if(left>=right){
return 0;
}
int mid = left + ((right - left)>>1);
int res = mergeSort(nums, left, mid);
res += mergeSort(nums, mid+1, right);
res += merge(nums, left, mid, right);
return res;
}
private int merge(int[] nums, int left, int mid, int right){
int[] arr = new int[right-left+1];
int p=0, p1 = left, p2=mid+1;
int res = 0;
while(p1<=mid && p2<=right){
if(nums[p1] > nums[p2]){
arr[p++] = nums[p1++];
res += right - p2 + 1;
}else{
arr[p++] = nums[p2++];
}
}
while(p1<=mid){
arr[p++] = nums[p1++];
}
while(p2<=right){
arr[p++] = nums[p2++];
}
for(int i=0; i<arr.length; i++){
nums[left+i] = arr[i];
}
return res;
}
}
class Solution {
public int reversePairs(int[] nums) {
if(nums==null || nums.length==0)
return 0;
return mergeSort(nums, 0, nums.length-1);
}
private int mergeSort(int[] nums, int left, int right){
if(left==right)
return 0;
int mid = left + ((right-left)>>1);
int leftRes = mergeSort(nums, left, mid);
int rightRes = mergeSort(nums, mid+1, right);
int curRes = merge(nums, left, mid, right);
return leftRes + rightRes + curRes;
}
private int merge(int[] nums, int left, int mid, int right){
int[] help = new int[right-left+1];
int p=0, p1=left, p2=mid+1;
int res = 0;
while(p1<=mid && p2<=right){
if(nums[p1] > nums[p2]){
res += right - p2 + 1;
help[p++] = nums[p1++];
}else{
help[p++] = nums[p2++];
}
}
while(p1<=mid){
help[p++] = nums[p1++];
}
while(p2<=right){
help[p++] = nums[p2++];
}
for(int i=0; i<help.length; i++){
nums[i+left] = help[i];
}
return res;
}
}
第三次做, 逆序版本的递归排序, 巩固基础
public class Solution {
public int InversePairs(int [] array) {
if(array==null || array.length==0)
return 0;
return mergeSort(array, 0, array.length-1);
}
public int mergeSort(int[] arr, int left, int right){
if(left==right)
return 0;
int mid = left + ((right - left)>>1);
int leftNum = mergeSort(arr, left, mid);
int rightNum = mergeSort(arr, mid+1, right);
int currNum = merge(arr, left, mid, right);
return (leftNum + rightNum + currNum) % 1000000007;
}
public int merge(int[] arr, int left, int mid, int right){
int[] help = new int[right - left + 1];
int i=0, p1=left, p2=mid+1, count=0;
while(p1<=mid && p2<=right){
if(arr[p1] > arr[p2]){
count += right - p2 + 1;
count = count%1000000007;
help[i++] = arr[p1++];
}
else
help[i++] = arr[p2++];
}
while(p1<=mid)
help[i++] = arr[p1++];
while(p2<=right)
help[i++] = arr[p2++];
for(int k=0; k<help.length; k++)
arr[left++] = help[k];
return count;
}
}
第二次做,舒服多了,写一个逆序的归并排序就行
public class Solution {
public int InversePairs(int [] array) {
if(array==null||array.length<2)
return 0;
return MergeSort(array, 0, array.length-1);
}
public int MergeSort(int[] arr, int left, int right){
if(left==right)
return 0;
int mid = left + ((right - left) >> 1);
int leftRes = MergeSort(arr, left, mid);
int rightRes = MergeSort(arr, mid+1, right);
int mergeRes = Merge(arr, left, mid, right);
return (leftRes + rightRes + mergeRes)%1000000007;
}
public int Merge(int[] arr, int left, int mid, int right){
int[] help = new int[right - left + 1];
int p1=left, p2=mid+1, i=0;
int res=0;
while(p1 <= mid && p2 <=right){
if(arr[p1] > arr[p2]){
res += right - p2 + 1;
res = res%1000000007;
help[i++] = arr[p1++];
}
else{
help[i++] = arr[p2++];
}
}
while(p1<=mid)
help[i++] = arr[p1++];
while(p2<=right)
help[i++] = arr[p2++];
for(int k=0; k<help.length; k++)
arr[left++] = help[k];
return res;
}
}
暴力解(不通过)
public class Solution {
public int InversePairs(int [] array) {
if(array.length<1)
return -1;
int count =0;
for(int i=0; i<array.length-1; i++)
for(int j=i+1; j<array.length; j++){
if(array[i] > array[j])
count++;
}
return count;
}
}
使用归并排序
笔记
- 掌握归并排序, 从前往后合并数组和从后往前合并数组都要掌握
思路
- 两大点
- (1)归并排序先将数组不断二等分成左右两边各1个元素, 比较这两个元素判断是否构成逆序对, 并将这个两个元素合并成一个有序的数组, 归并后的数组因为是递增排序的,所以不再包含逆序对
- (1)接着比较长度为2的两个数组, 首先这两个数组内部没有逆序对了, 所以只有在合并两个数组的过程中包含逆序对
- (1)综上, 使用归并排序计数逆序对时, 只有在合并两个数组时才有可能出现逆序对
- (2)如何计数? 这里还要注意, 不同于以往的从前往后合并数组, 这里要从后往前(每次将更大的数赋值给原数组)合并数组, 为什么
- 首先左右两个数组都是有序的, 各自内部不会有逆序对, 所以只有当左边数组的元素比右边数组元素大的时候才会产生逆序对
- 如果arr[left] > arr[right], 那么arr[left]这个元素比arr[mid+1],arr[mid+2],…,arr[right]都大, 此时产生的逆序对数量为 right-mid. 令a[k] = arr[left–], 进行下一轮判断
- 假设从前往后比较的话, 如果arr[left] > arr[right], 此时不清楚 arr[left]会不会比arr[right+1],arr[right+2]大, 所以不能一次性就知道arr[left] > arr[right]直接会产生多少个逆序对!
public class Solution {
private int[] aux;
private int count=0;
public int InversePairs(int [] array) {
if(array.length<1)
return -1;
aux = new int[array.length];
MergeCore(array, 0, array.length-1);
return count%1000000007;
}
public void MergeCore(int[] array, int lo, int hi){
if(lo!=hi){
int mid = (lo + hi) >> 1;
MergeCore(array, lo, mid);
MergeCore(array, mid+1, hi);
Merge(array, lo, mid, hi);
}
}
public void Merge(int[] a, int lo, int mid, int hi){
int i=mid, j=hi;
for(int k=hi; k>=lo; k--)
aux[k] = a[k];
for(int k=hi; k>=lo; k--){
if(i<lo)
a[k] = aux[j--];
else if(j<mid+1)
a[k] = aux[i--];
else if(aux[i] <= aux[j])
a[k] = aux[j--];
else{
a[k] = aux[i--];
count = count + j - mid;
if(count>1000000007)
count = count%1000000007;
}
}
}
}