【剑指Offer】**数组中的逆序对(归并排序!)

题目描述

在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。

示例 1:
输入: [7,5,6,4]
输出: 5

限制:
0 <= 数组长度 <= 50000

归并排序

leetcode官方视频讲解很不错!

大概思路:

  • 归并排序:递归的将数组拆分为两个分组,直到拆分至分组个数为1(因为1个数的时候默认是有序的),然后开始回升,不断地去有序合并被拆分的两个分组(此时的两个分组已经排好序了)。
  • 与普通归并排序不同的是,在两个分组合并的时候需要去计算逆序对。
  • 怎么计算?具体来说,两个分组已经都是各自有序的,合并的时候是先借用辅助数组来存储排序前的两个分组,然后双指针比较,谁小谁先放入原来数组中。如果是右边那个分组的指针所指的数更小,则放入原来数组,且说明左边分组中所有剩下的数都比这个数大,那就计入逆序对数量中。
  1. 时间复杂度:同归并排序O(NlogN)。递归分解数据,需要递归logN次,每次都需要对N个数据扫描一次,最好最坏平均都一样,所以O(NlogN)
  2. 空间复杂度:同归并排序O(N)。用到了辅助空间,每次合并之前都会把未合并的顺序存到辅助空间,然后读比较temp数组,结果写存到原来数组里。与其每次合并的时候去分配空间,不如一次性开辟好,更节省空间

什么是归并排序?

  • 归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。
  • 算法描述:
  1. 把长度为n的输入序列分成两个长度为n/2的子序列;
  2. 对这两个子序列分别采用归并排序;
  3. 将两个排序好的子序列合并成一个最终的排序序列。
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); //归并排序
    }
};
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值