归并排序的应用

归并排序

应用1:统计逆序对个数

采用经典的基于分治策略的归并排序来实现。

  • m i d = n u m s . l e n g t h 2 mid = \frac{nums.length}{2} mid=2nums.length
  • 在归并排序的过程中统计逆序对的个数
  • 设序列nums[0:mid]和nums[mid+1:]两者都按升序排好了
    • 则原问题逆序对的个数为:nums[0:mid]中逆序对的个数(子问题1),nums[mid+1:]中逆序对的个数(子问题2),以及两个序列之间的逆序对个数的总和。
    • 如何计算之间的逆序对个数?按归并排序的思路!设l初始时指向nums[0:mid]的第一个元素,r初始时指向nums[mid+1:]的第一个元素
      • 如果nums[l] > nums[r],则由于nums[0:mid]已经升序排好了,nums[0:mid]中从l开始的所有元素都与nums[r]构成一个逆序对
      • 否则nums[l]不会与nums[r]及其后面的任何一个元素构成逆序对(因为nums[mid+1:]也已经按升序排好了),可以不予考虑。
class Solution {
    int ans = 0;
    public void mergesort(int l,int r,int[] nums)
    {
        if(l>=r)
            return;
        int mid = (l + r)/2;
        mergesort(l,mid,nums);
        mergesort(mid+1,r,nums);
        int[] left = new int[mid - l + 1];
        System.arraycopy(nums,l,left,0,left.length);
        int p2 = mid+1;  //指向right
        mid = 0;  //指向left
        int i = l;   //计数变量
        while(mid<left.length && p2 <= r)
        {
            if(left[mid] > nums[p2])
            {
                ans+=(left.length - mid);
                nums[i++] = nums[p2++];
            }
            else
            {
                nums[i++] = left[mid++];
            }
        }
        while(mid<left.length)
            nums[i++] = left[mid++];
        while(p2 <= r)
            nums[i++] = nums[p2++];
    }

    public int reversePairs(int[] nums) {
        mergesort(0,nums.length - 1,nums);
        return ans;
    }

    public static void main(String[] args)
    {
        int[] nums = {7,5,6,4};
        new Solution().reversePairs(nums);
    }
}

优化后的代码:不每次新生成一个left数组

  • 注意left长度最多为nums长度的一般再加1
class Solution {
    int ans = 0;
    public void mergesort(int l,int r,int[] nums,int[] left)
    {
        if(l>=r)
            return;
        int mid = (l + r)/2;
        mergesort(l,mid,nums,left);
        mergesort(mid+1,r,nums,left);
        System.arraycopy(nums,l,left,0,mid - l + 1);
        int p2 = mid+1;  //指向right
        int length = mid - l + 1;
        mid = 0;  //指向left
        int i = l;   //计数变量
        while(mid<length && p2 <= r)
        {
            if(left[mid] > nums[p2])
            {
                ans+=(length - mid);
                nums[i++] = nums[p2++];
            }
            else
            {
                nums[i++] = left[mid++];
            }
        }
        while(mid<length)
            nums[i++] = left[mid++];
        while(p2 <= r)
            nums[i++] = nums[p2++];
    }

    public int reversePairs(int[] nums) {
        int[] left = new int[nums.length / 2 + 1];
        mergesort(0,nums.length - 1,nums,left);
        return ans;
    }

}

应用2:统计数组中每个元素右侧小于其自身的元素个数

  • 注意precount的使用
    • 合并时nums[0:mid]处元素已经按升序排好,所以nums[0:mid]中(nums[0:mid]在前面的元素后面小于其自身的元素)一定是(nums[0:mid]在后面的元素小于其自身的元素 )
  • 第一次做时总是超时:
    • 原因1时没有使用precount充分利用前后元素的关系
    • 原因2是不需要每次都copy所有数组cursor的值到cursor_pre中
    • 只需要copy从l区间到r区间的即可
class Solution {
    public void mergesort(int[] count,int l,int r,int[] nums,int[] left,int[] cursor,int[] cursor_pre)
    {
        if(l>=r)
            return;
        int mid = (l + r)/2;
        mergesort(count,l,mid,nums,left,cursor,cursor_pre);
        mergesort(count,mid+1,r,nums,left,cursor,cursor_pre);
        System.arraycopy(nums,l,left,0,mid - l + 1);
        System.arraycopy(cursor,l,cursor_pre,l,r - l + 1);
        int p2 = mid+1;  //指向right
        int length = mid - l + 1;
        mid = 0;  //指向left
        int i = l;   //计数变量
        int pre_count = 0;
        while(mid<length && p2 <= r)
        {
            if(left[mid] > nums[p2])
            {
                pre_count++;
                cursor[i] = cursor_pre[p2];
                nums[i++] = nums[p2++];
            }
            else
            {
                count[cursor_pre[l+mid]]+=pre_count;
                cursor[i] = cursor_pre[l + mid];
                nums[i++] = left[mid++];
            }
        }
        while(mid<length)
        {
            count[cursor_pre[l+mid]]+=pre_count;
            cursor[i] = cursor_pre[l + mid];
            nums[i++] = left[mid++];
        }
        while(p2 <= r) {
            cursor[i] = cursor_pre[p2];
            nums[i++] = nums[p2++];
        }
    }

    public List<Integer> countSmaller(int[] nums) {
        int[] left = new int[nums.length / 2 + 1];
        int[] cursor_pre = new int[nums.length];
        int[] cursor = new int[nums.length];
        for(int i = 0;i<nums.length;i++)
            cursor[i] = i;
        int[] count = new int[nums.length];
        mergesort(count,0,nums.length - 1,nums,left,cursor,cursor_pre);
        List<Integer> ans = new ArrayList<>();
        for(int i : count)
        {
            ans.add(i);
        }
        return ans;
    }
}```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值