树状数组解析

时间:2022-07-24
本文章向大家介绍树状数组解析,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

树状数组所能解决的典型问题就是存在一个长度为n的数组,我们如何高效进行如下操作:

update(idx, delta):将num加到位置idx的数字上。

prefixSum(idx):求从数组第一个位置到第idx(含idx)个位置所有数字的和。

rangeSum(from_idx,to_idx):求从数组第from_idx个位置到第to_idx个位置的所有数字的和

lowbit 操作

意思是获取这个数的展开二进制的最低的2的幂方数

lowbit = x & -x;

树状数组的思路是将数组的前缀和拆分为不同的多个数组,正好利用2的幂次方可以将其拆分为log(n) 的时间复杂度

树状数组的定义

定义第i个位置记录(i-lowbit(i),i)数字和; i 位置的父节点是 i + lowbit(i)

性质: 第i个节点的位置只能由其祖先节点进行覆盖

使用树状数组求范围和,可以采用前缀和之差来进行计算

public class TreeArray {

    int[] tree;
    int[] arr;

    public TreeArray(int[] nums){
        this.arr = nums;
        tree = new int[nums.length + 1];
    }

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

    public void build_tree(){
        // 这里使用更新操作可以降低操作的复杂度
        for(int i=0;i<arr.length;i++){
           tree[i+1] = arr[i];
        }
        for (int i=1;i<tree.length;i++){
            int j = i + lowbit(i);
            if (j < tree.length){
                tree[j] += tree[i];
            }
        }
    }

    // 将数组中的某位增加值,
    public void update_tree(int idx, int val){
        // 这里主要是因为树状数组,都向后移动一位,给0做查询结束的判断
        idx += 1;
        while (idx < tree.length){
            tree[idx] += val;
            idx = idx + lowbit(idx);
        }
    }

    // 求数组的前缀和
    public int query_sum(int idx){
        idx += 1;
        int res = 0;
        while (idx > 0){
            res += tree[idx];
            idx = idx - lowbit(idx);
        }
        return res;
    }
}

315. 计算右侧小于当前元素的个数

给定一个整数数组 nums,按要求返回一个新数组 counts。数组 counts 有该性质: counts[i] 的值是 nums[i] 右侧小于 nums[i] 的元素的数量。

示例:

输入:nums = [5,2,6,1] 输出:[2,1,1,0]

class Solution {
    private int[] c;
    private int[] a;

    public List<Integer> countSmaller(int[] nums) {
        List<Integer> resultList = new ArrayList<Integer>();
        // 去重排序
        discretization(nums);
        // 初始化比原数组大5的数组进行填0
        init(nums.length + 5);
        for (int i = nums.length - 1; i >= 0; --i) {
            //
            int id = getId(nums[i]);
            // 查询在这个范围内元素个数,并添加进行结果集
            resultList.add(query(id - 1));
            // 在当前位置+1
            update(id);
        }
        Collections.reverse(resultList);
        return resultList;
    }

    private void init(int length) {
        c = new int[length];
        Arrays.fill(c, 0);
    }

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

    private void update(int pos) {
        while (pos < c.length) {
            c[pos] += 1;
            pos += lowBit(pos);
        }
    }

    private int query(int pos) {
        int ret = 0;
        while (pos > 0) {
            ret += c[pos];
            pos -= lowBit(pos);
        }

        return ret;
    }

    // 去重排序
    private void discretization(int[] nums) {
        Set<Integer> set = new HashSet<Integer>();
        for (int num : nums) {
            set.add(num);
        }
        int size = set.size();
        a = new int[size];
        int index = 0;
        for (int num : set) {
            a[index++] = num;
        }
        Arrays.sort(a);
    }

    private int getId(int x) {
        return Arrays.binarySearch(a, x) + 1;
    }
}