剑指offer面试题51(java版):数组中的逆序对

welcome to my blog

剑指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) {
        //input check; 没有这个input checko会造成栈溢出错误, left=0,right=-1....
        if(nums==null || nums.length==0)
            return 0;
        return mergeSort(nums, 0, nums.length-1);
    }
    
    //递归函数逻辑: 先在[left,mid]上归并排序, 再在[mid+1,right]上归并排序, 最后将[left,mid]和[mid+1,right]合并
    private int mergeSort(int[] nums, int left, int right){
        //base case
        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;
    }
    // 必须是降序排序! 否则结果会出错, 
    // 假如还是升序, 那么merge{4,5,6}和{1,2,3}时, p1指向4, p2指向1, 第一次循环res=1,p2++; 第二次循环res=3,p2++; 第三次循环res=6,p2++; 但实际上4能构成的逆序对只有3个
    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){
        //base case
        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){
        //create auxiliary arr
        int[] help = new int[right - left + 1];
        //create pointer
        int p1=left, p2=mid+1, i=0;
        //create res
        int res=0;
        //start merging
        while(p1 <= mid && p2 <=right){
            if(arr[p1] > arr[p2]){
                res += right - p2 + 1; //arr[p1] 比arr[p2]大的话,比它后面所有的数都大
                res = res%1000000007; //不加这句,对于大数组的案例就通过不了了; 本来我是加在return中的,后来发现报错才加在这里的
                help[i++] = arr[p1++];
            }
            else{
                help[i++] = arr[p2++];
            }
        }
        //ensure merging finished
        while(p1<=mid)
            help[i++] = arr[p1++];
        while(p2<=right)
            help[i++] = arr[p2++];
        //last step: help ==> arr
        for(int k=0; k<help.length; k++)
            arr[left++] = help[k];
        return res;
    }
}
暴力解(不通过)
  • 写一写暴力解感受感受
public class Solution {
    public int InversePairs(int [] array) {
        //input check
        if(array.length<1)
            return -1;
        //execute
        //暴力解法
        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) {
        //input check
        if(array.length<1)
            return -1;
        //execute
        //使用归并排序的思想: 先拆分,再排序. 在比较元素大小的时候统计逆序对的总数
        //要不要使用一个全局变量计数?
        aux = new int[array.length];
        MergeCore(array, 0, array.length-1);
        return count%1000000007;
    }
    public void MergeCore(int[] array, int lo, int hi){
    // 每次递归的作用: 合并左右两个有序的数组
        //recursion finish
        //if(lo==hi)
        //    return;
        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{ // aux[i] > aux[j]
                a[k] = aux[i--];
                count = count + j - mid;
                if(count>1000000007)
                    count = count%1000000007;
            }
        }
    }
}
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值