树状数组+离散化
树状数组的优点
- 快速修改某个数 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[1∼x]的元素相加
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 i≤j)。
提示:
- 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]<=231−1
- − 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,(1≤j≤i−1)来计算
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
lower≤sum[i]−sum[j]≤upper⇒sum[i]−upper≤sum[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;
}
}