树状数组求逆序对详解

树状数组通用模版

class BIT {
        int n;  //这个是外部传进来数组的大小
        int[] c;    //这个是预处理的树状数组

        public BIT(int n) {
            this.n = n;
            c = new int[n + 1]; //树状数组一定要开n+1,从1开始,不然的话 ,add操作 index = index+lowbit(index),index为0就造成了死循环
        }

        public void add(int index, int val) {
            //默认模版是,除了更新它自己 还要更新它的父亲。一直更新下去,但是碰到逆序对这种,只要饿更新它自己即可
            while (index <= n) {
                c[index] += val;
                index += lowbit(index);
            }
        }

        //查询原数组 0 ~ index之内的和
        public int query(int index) {
            int res = 0;
            while (index > 0) {
                res += c[index];
                index -= lowbit(index);
            }
            return res;
        }

        //如果要查询给定区间内的和 比如 i~j 区间内的和,就是 query[j] - query[i-1]
        public int querySum(int from, int to) {
            return query(to) - query(from - 1);
        }

        private int lowbit(int x) {
            return x & (-x);
        }
    }

利用树状数组求逆序对,具体思路写在注释中了

LeetCode 逆序对

/**
     * 树状数组写法
     * 根据题意分析,i < j 且 nums[i] > 2*nums[j],这个条件我们可以解读为,当前元素后方有多少元素比这个元素的一半还小
     * 推荐博客 https://blog.csdn.net/gonganDV/article/details/88817416、https://blog.csdn.net/S_999999/article/details/99076076
     *
     * 简要思路:
     *      我们知道逆序对的定义是 i < j 且 a[i] > a[j]
     *【为什么要用树状数组】
     *      当数据的范围较小时,比如 下标最大为len,那么我们可以开一个数组c[len],来记录前面数据的出现情况,初始化为0;当数据a出现时,就令c[a]=1。
     *      这样的话,欲求某个数a的逆序数,只需要算出在当前状态下c[a+1,len]中有多少个1,因为这些位置的数在a之前出现且比a大。
     *      但是若每添加一个数据a时,就得从a+1到 len搜一遍,复杂度太高了
     *
     *【怎么用树状数组】
     *      用树状数组能够很好的解决这个问题。把数按照数组中的顺序一个个插入到树状数组中,每插入一个数,就统计比它小的数的个数
     *      对应的逆序为 i - query(c[i]),其中 i 为当前已经插入的数的个数, query(c[i])为比 c[i] 小的数的个数,i- query(c[i]) 即比c[i] 大的个数, 即逆序的个数。
     *      最后需要把所有逆序数求和,就是在插入的过程中边插入边求和.
     *
     *【怎么写】
     *      构建一个值的范围是1~n的BIT,按照j = 0,1,2,···,n-1的顺序进行如下操作。
     *          1、BIT c[j] 位置加上 1
     *          2、res += j - query(c[i])
     *
     *【更进一步】
     *      当数组长度较大时,直接开数组显然是不行了,这是的解决办法就是离散化。
     *      假如现在有一些数:1234 98756 123456 99999 56782,由于1234是第一小的数,所以num[1]=1;
     *      依此,有 num[5] = 2, num[2] = 3, num[4] = 4, num[3] = 5; 这样转化后并不影响原来数据的相对大小关系
     *
     */

    class BIT{
        int n;
        int[] c;

        public BIT(int n) {
            this.n = n;
            c = new int[n + 1];
        }

        public void add(int i, int val) {
            while (i <= n) {
                c[i] += val;
                i += i & -i;
            }
        }

        public int query(int i) {
            int res = 0;
            while (i > 0) {
                res += c[i];
                i -= i & (-i);
            }
            return res;
        }
    }

    public int reversePairs(int[] nums) {
        //离散化
        int n = nums.length;
        if (n == 0) return 0;
        int[] arr = Arrays.stream(nums).distinct().sorted().toArray();
        Map<Integer, Integer> map = new HashMap<>();
        for (int i = 0; i < arr.length; i++) {
            map.put(arr[i], i + 1);
        }
        int res = 0;
        BIT bit = new BIT(n);
        for (int i = 1; i <= nums.length; i++) {
            bit.add(map.get(nums[i - 1]), 1);
            res += i - bit.query(map.get(nums[i - 1]));
        }
        return res;
    }
发布了43 篇原创文章 · 获赞 6 · 访问量 3907

猜你喜欢

转载自blog.csdn.net/weixin_44424668/article/details/104267735