“这些事儿在熟练之后,也许就像喝口水一样平淡,但却能给初学者带来巨大的快乐,我一直觉得,能否始终保持如初学者般的热情、专注,决定了在做某件事时能走多远,能做多好。” 该系列文章由python编写,所刷题目共三个来源:之前没做出来的 ;Leetcode中等,困难难度题目; 周赛题目;某个专题的经典题目,所有代码已AC。每日1-3道,随缘剖析,希望风雨无阻,作为勉励自己坚持刷题的记录。
我的理解是树状数组维持的是与【前缀】相关的数据。这些数据可以用于【静态】和大多数的区间情况。使用简单。其结点维护信息的数量是由其结点转化成二进制之后最左边的1的个数之后的零的个数决定的。
但树状数组并不是单纯的前缀,而是以树的结构存储前缀,譬如c2,c4结点存储的才是真正意义上的前缀。规律是:
c [ i ] = A [ i − 2 k + 1 ] + A [ i − 2 k + 2 ] + . . . + A [ i ] c[i] = A[i - 2k+1] + A[i - 2k+2] + ... + A[i] c[i]=A[i−2k+1]+A[i−2k+2]+...+A[i]
其中k为i的二进制中从最低位到高位连续零的长度,例如i = 8(1000)时候,k = 3,可自行验证,这也是c[i]所管辖的范围。
计算非负整数 n 在二进制表示下 【最低位的 1 及其后边所有的 0】 构成的数值,比如 l o w b i t ( 6 ) = 2 lowbit(6) = 2 lowbit(6)=2 。公式为 l o w b i t ( n ) = n & ( ∼ n + 1 ) = n & ( − n ) lowbit(n) = n\&(∼n+1) = n\&(−n) lowbit(n)=n&(∼n+1)=n&(−n)。
简单解释一下,取反后加一的实际操作就是把【最低位1之前的数全部取反了】,也是补码的表示方式!负数的补码就是自身取反加一嘛。
这个 l o w b i t ( n ) lowbit(n) lowbit(n)是一种很简洁的从十进制中提取了二进制的思想,应用广泛。
我们以计算11的前缀和为例,11的二进制是1011,期望将其拆成1000+0010+0001,而树状数组中,有 p r e [ 11 ] = c [ 11 ] + c [ 10 ] + c [ 8 ] pre[11] = c[11]+c[10]+c[8] pre[11]=c[11]+c[10]+c[8],更直观的,我们可以沿着11一直向左上角找,直到剩0。
```
// 计算i的前缀和
while (i > 0){
res += c[i];
i -= lowbit(i);
}
```
对于当前更新的i,一定有某个比它大的区间包含它,而我们要做的就是找到这个【刚刚补齐的二进制数】,比如c[9]更新后,包含其的上一层为c[10]也就是c[9+1]。直到更新到最大的区间。
```
// 将第i个结点加x,n是BIT的大小(下标从1开始)
while (i <= n){
c[i] += x;
i += lowbit(i);
}
```
class Solution:
def reversePairs(self, nums: List[int]) -> int:
temp = []
res = 0
for t in reversed(nums):
# bisect是python内置模块,用于有序序列的插入和查找。返回的值是应该插入的位置。
curr = bisect_left(temp, t)
res += curr
temp[curr:curr] = [t]
return res
class BIT:
def __init__(self, n):
self.n = n
self.tree = [0] * (n + 1)
@staticmethod
def lowbit(x):
return x & (-x)
def query(self, x):
ret = 0
while x > 0:
ret += self.tree[x]
x -= BIT.lowbit(x)
return ret
def update(self, x):
while x <= self.n:
self.tree[x] += 1
x += BIT.lowbit(x)
class Solution:
def reversePairs(self, nums: List[int]) -> int:
n = len(nums)
# 离散化:一般有两种方式。利用字典映射 或 改变绝对位置成相对位置。这里是用的后者。
tmp = sorted(nums)
for i in range(n):
nums[i] = bisect.bisect_left(tmp, nums[i]) + 1
# 树状数组统计逆序对
bit = BIT(n)
ans = 0
# 把后面的放进来,计算前缀
for i in range(n - 1, -1, -1):
ans += bit.query(nums[i] - 1)
bit.update(nums[i])
return ans
class Solution:
def goodTriplets(self, nums1: List[int], nums2: List[int]) -> int:
n = len(nums1)
# 按照nums1中的出现顺序,构成f,记录了当前位置数值在nums2的出现位置!
# 这个题和数值大小本身无关,仅与下标位置有关
pos2 = dict((nums2[i], i) for i in range(n))
f = [pos2[nums1[i]] for i in range(n)]
def add(x):
while x <= n:
t[x] += 1
x += (x & (-x))
def query(x):
res = 0
while x:
res += t[x]
x -= (x & (-x))
return res
left, right = [0] * n, [0] * n
# 计算左侧小于 f[i] 的元素个数
t = [0] * (n + 1)
for i in range(n):
left[i] = query(f[i])
add(f[i] + 1)
# 计算右侧大于 f[i] 的元素个数
t = [0] * (n + 1)
for i in range(n-1, -1, -1):
right[i] = n - 1 - i - query(f[i] + 1)
add(f[i] + 1)
res = 0
for i in range(n):
res += left[i] * right[i]
return res