翻转对问题

一、题目

 链接:493. 翻转对 - 力扣(LeetCode) 

 题目:

     给定数组 nums,如果 i < j 且 nums[i] > 2*nums[j]就将 (i, j) 称作一个翻转对。

    返回数组中翻转对数量。

示例 :

    输入: [2,4,3,5,1]
    输出: 3

二、分析

1) 暴力法

        数组左侧有一个i下标,右侧有一个j下标,找出所有满足nums[i] > 2*nums[j]的个数,最简单的思想就是暴力,遍历每一个i,向后一直寻找满足条件的j,但时间复杂度是O(n^2)

2) 归并分治

        利用归并分治的思想,将数组从中间一分为二,假如左右侧范围数据都是有序的(从大到小),那么

        总翻转对数 = 左侧范围翻转对数 + 右侧范围翻转对数+跨左右范围翻转对数

        这里解释一下公式怎么来的:

                ① 将数组分为左[left, mid], 右[mid + 1, right]两部分

                ② 右侧[mid + 1, right]正常求出翻转对个数即可

                ③ 左侧[left, mid]是i的范围,但是j在i的右侧,所以j的范围为

                [i+1, mid] + [mid + 1, right],

                所以在求出j在[i + 1, mid]范围后,还需要求出跨左右范围[mid + 1, right]

        这里将一下跨左右范围翻转对怎么求:

                因为左右范围数据是有序的,假设左范围为[9, 8, 7, 6], 右范围为[5, 4, 3, 2],L指针指向左侧范围数据,R指针指向右侧范围数据,

        当L=0时,只需要让R在右侧范围向右寻找第一个nums[L] > 2*nums[R]的位置

 

        那么可以知道R往后所有的数据都与L满足翻转对,因此L位置跨左右范围翻转对的数量为right-R + 1

        当L=1时,R并不需要回退,R之前的位置nums[L] <= 2*nums[R],现在L后移之后nums[L]又变小了,所以R之前范围肯定不成立,

        因此R只需要从当前位置一直向后找即可

        ④ 既然假设了范围要有序,那么要求出跨左右范围后,要对左右范围有序序列进行从大到小贪心合并

三、代码实现

class Solution {
    //	归并分治的辅助数组
    private static final int[] help = new int[50000];

    public static int reversePairs(int[] nums) {
        return reversePairs(nums, 0, nums.length - 1);
    }
    private static int reversePairs(int[] nums, int left, int right) {
        //	范围内只有一个数据,翻转对个数一定为0,并且自然有序
        if (left == right) {
            return 0;
        }
        //	为了防止范围溢出,这里用left加上范围的一半,求出中间下标
        int mid = left + ((right - left) / 2);
        //  求出左侧范围翻转对数量、右侧范围翻转对数量
        int leftCount = reversePairs(nums, left, mid);
        int rightCount = reversePairs(nums, mid + 1, right);

        //  求出跨左右范围翻转对数量
        int allCount = leftCount + rightCount;

        //	遍历左侧范围的每一个位置i,找到右侧满足翻转对的个数
        for (int i = left, j = mid + 1; i <= mid; i++) {
            //  从j往后找到第一个满足nums[i] > 2 * nums[j]条件的
            while (j <= right && nums[i] <= 2L * nums[j]) {
                j++;
            }
            //	记录这个i位置对应的翻转对个数
            allCount += right - j + 1;
        }

        //  将序列按照从大到小进行合并
        //	1.将两个序列合并到辅助数组help
        for (int i = left, j = mid + 1, c = left; i <= mid || j <= right; c++) {
            int lValue = i <= mid ? nums[i] : Integer.MIN_VALUE;
            int rValue = j <= right ? nums[j] : Integer.MIN_VALUE;
            if (lValue >= rValue) {
                help[c] = lValue;
                i++;
            } else {
                help[c] = rValue;
                j++;
            }
        }
		//	2.将合并好的序列拷贝至原数组
        for (int i = left; i <= right ; i++) {
            nums[i] = help[i];
        }
		
        //	返回翻斗数总个数
        return allCount;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值