树状数组+离散化

树状数组的优点

  • 快速修改某个数 O ( l o g n ) O(logn) O(logn)
  • 快速求前缀和 O ( l o g n ) O(logn) O(logn)

对比原数组:

  • 修改某个数 O ( 1 ) O(1) O(1)
  • 求前缀和 O ( n ) O(n) O(n)

对比前缀和数组

  • 修改某个数 O ( n ) O(n) O(n)
  • 求前缀和 O ( 1 ) O(1) O(1)

树状数组的实现

原理看博客:主要就是使用二进制思想,将原数组分成块状,进而对其进行修改和求和
主要两个操作:

  • 修改操作:在某个位置+c,就是在当前块和后续块+c
public void add(int x, int c) {
	for(int i = x; i <= n; i += lowbit(i)) {
		tr[i] += c;
	}
}
  • 求和操作:将 a [ 1 ∼ x ] a[1\sim x] a[1x]的元素相加
public int sum(int x) {
	int res = 0;
	for(int i = x; i >= 1; i -= lowbit(i)) {
		res += tr[i];
	}
}

(其中,lowbit(x)是求x的二进制表示中最后一位1,即x&(-x))

  • 但是题目中经常会出现n很大的情况,例如 n > 1 0 6 n>10^6 n>106,或者存在负数的情况,那么树状数组就会非常大或者下标越界,导致内存溢出。所以在此时就需要原数组进行离散化。离散化有两步操作,分别是排序+去重,这个可以借助有序集合实现。但是用有序集合实现后,对应的数下标无法快速确定,此时需要借助哈希表,将有序集合中的数映射到[1,n]上,此时就满足了树状数组的要求。(离散化实现:有序集合+哈希表

例题

LeetCode327. 区间和的个数

给你一个整数数组 n u m s nums nums 以及两个整数 l o w e r lower lower u p p e r upper upper。求数组中,值位于范围 [ l o w e r , u p p e r ] [lower, upper] [lower,upper] (包含 l o w e r lower lower u p p e r upper upper)之内的区间和的个数 。

区间和 S ( i , j ) S(i, j) S(i,j) 表示在 n u m s nums nums中,位置从 i i i j j j的元素之和,包含 i i i j j j ( i ≤ j i ≤ j ij)。

提示:

  • 1 < = n u m s . l e n g t h < = 1 0 5 1 <= nums.length <= 10^5 1<=nums.length<=105
  • − 2 31 < = n u m s [ i ] < = 2 31 − 1 -2^{31} <= nums[i] <= 2^{31} - 1 231<=nums[i]<=2311
  • − 1 0 5 < = l o w e r < = u p p e r < = 1 0 5 -10^5 <= lower <= upper <= 10^5 105<=lower<=upper<=105
  • 题目数据保证答案是一个 32 位 的整数

思路:
使用前缀和求出 s u m [ i ] sum[i] sum[i],那么对于 i i i,可以暴力遍历 j , ( 1 ≤ j ≤ i − 1 ) j,(1\leq j\le i-1) j,(1ji1)来计算 s u m [ i ] − s u m [ j ] sum[i]-sum[j] sum[i]sum[j]来得到满足区间和 [ l o w e r , u p p e r ] [lower,upper] [lower,upper]的个数。此时整个的时间复杂度 O ( n 2 ) O(n^2) O(n2),必然超时。此时想到可以使用树状数组来求解。
l o w e r ≤ s u m [ i ] − s u m [ j ] ≤ u p p e r ⇒ s u m [ i ] − u p p e r ≤ s u m [ j ] ≤ s u m [ i ] − l o w e r lower\leq sum[i]-sum[j]\leq upper\Rightarrow sum[i]-upper\leq sum[j]\leq sum[i]-lower lowersum[i]sum[j]uppersum[i]uppersum[j]sum[i]lower
即计算在 [ s u m [ i ] − u p p e r , s u m [ i ] − l o w e r ] [sum[i]-upper,sum[i]-lower] [sum[i]upper,sum[i]lower]的数的个数。使用树状数组可以快速在 O ( l o g n ) O(logn) O(logn)下得到。但是 s u m [ i ] sum[i] sum[i]的数据范围很大,对应的树状数组 t r [ n + 1 ] tr[n+1] tr[n+1]也很大,且会越界。所以需要离散化。离散化借助有序集合+哈希表即可。离散化需要将所有用到的数都存入有序集合中,然后将这些数映射到 [ 1 , m a x ] [1,max] [1,max]。后续操作只需到对 [ 1 , m a x ] [1,max] [1,max]上进行操作即可。

class Solution {

    int[] tr;
    int max;

    public int countRangeSum(int[] nums, int lower, int upper) {
        int n = nums.length;
        long[] sum = new long[n+1];
        for(int i = 1; i <= n;i++) {
            sum[i] = sum[i-1] + nums[i-1];
        }
        TreeSet<Long> set = new TreeSet<>();
        set.add(0l);
        for(long x : sum) {
            set.add(x);
            set.add(x - lower);
            set.add(x - upper - 1);
        }
        Map<Long,Integer> map = new HashMap<>();
        int idx = 1;
        for(long x : set) {
            map.put(x,idx++);
        }
        tr = new int[idx+1];
        max = idx;
        int res = 0;
        for(long x : sum) {
            res += sum(map.get(x - lower)) - sum(map.get(x - upper - 1));
            add(map.get(x),1);
        }
        return res;
    }

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

    public void add(int x, int c) {
        for(int i = x; i <= max;i += lowbit(i)) {
            tr[i] += c;
        } 
    }

    public int sum(int x) {
        int res = 0;
        for(int i = x; i >= 1; i -= lowbit(i)) {
            res += tr[i];
        }
        return res;
    }
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值