树状数组(Fenwick Tree)介绍
树状数组现多用于高效计算数列的前缀和以及区间和。树状数组划分区间所依据的原理是任何正整数都能表示为2的幂相加的形式,如 19 = 2 4 + 2 1 + 2 0 19 = 2^4 + 2^1 + 2^0 19=24+21+20。其主要有单点更新和区间查询这两个功能。我们以剑指offer 051题来看,题目是找到数组中的逆序对,例如[7, 5, 6, 4]中有“[7, 5], [7, 6], [7, 4], [5, 4], [6, 4]”五对逆序对,其代码如下:
class Solution:
def reversePairs(self, nums: List[int]) -> int:
class FenwickTree:
def __init__(self, n):
self.size = n
self.tree = [0 for _ in range(n + 1)]
def __lowbit(self, index):
return index & (-index)
# 单点更新:将 index 这个位置 + 1
def update(self, index, delta):
# 从下到上,最多到 size,可以等于 size
while index <= self.size:
self.tree[index] += delta
index += self.__lowbit(index)
# 区间查询:查询小于等于 index 的元素个数
# 查询的语义是"前缀和"
def query(self, index):
res = 0
# 从上到下,最少到 1,可以等于 1
while index > 0:
res += self.tree[index]
index -= self.__lowbit(index)
return res
size = len(nums)
if size == 0 or size == 1: return 0
# 去重
s = list(set(nums))
len_s = len(s)
import heapq
heapq.heapify(s)
rank_map = dict()
rank = 1
for _ in range(len_s):
num = heapq.heappop(s)
rank_map[num] = rank
rank += 1
fenwick_tree = FenwickTree(len_s)
res = 0
# 从后向前填表
for index in range(size - 1, -1, -1):
# 1、查询排名
rank = rank_map[nums[index]]
# 2、在树状数组排名的那个位置 + 1
fenwick_tree.update(rank, 1)
# 3、查询一下小于等于“当前排名 - 1”的元素有多少
res += fenwick_tree.query(rank - 1)
return res
这里每个节点存储的便是该位置的数字出现的次数,首先将数组离散化,按照其大小排名顺序放入树状数组中,防止树状数组过大。
__lowbit函数代表该节点覆盖的区间长度,我们将树状数组画出来就比较明显了,其完整结构为:
将没有数字的节点删除,因为其可以表示为上层节点和兄弟节点的差,去除冗余后为:
比如5覆盖的区间长度只有1,6覆盖的区间长度为2,4覆盖的区间长度为4,8覆盖了所有值,其区间长度即为8。从数学角度上来看,
x
&
(
−
x
)
x \& (-x)
x&(−x)意味着假设二进制的
x
x
x中最后一个“1”所在的位数为
k
k
k,则
x
&
(
−
x
)
=
2
(
k
−
1
)
x \& (-x) = 2^(k - 1)
x&(−x)=2(k−1)。比如我要求原本数组中前六个的值的和,我们首先找到的便是树状数组中的节点6,减去自己的长度,找到节点4,再减去区间长度,发现等于0了,说明照完了,最终的值就是节点6和节点4的和。更新的时候也是,假如你更新了节点5,那么节点6覆盖了节点5,便需要也更新节点6,同理节点8也覆盖了节点6,节点8也需要更新,如何找到覆盖了自己的节点,便直接加上
x
&
(
−
x
)
x \& (-x)
x&(−x)即可。
leetcode-315题和这一题几乎一样,便是在最后处理res变量的时候,一题将其累加起来,另一题将每一个值都放入列表中输出。315题的代码如下:
class Solution:
def countSmaller(self, nums: List[int]) -> List[int]:
class FenwickTree:
def __init__(self, n):
self.size = n
self.tree = [0 for _ in range(n + 1)]
def __lowbit(self, index):
return index & (-index)
# 单点更新:将 index 这个位置 + 1
def update(self, index, delta):
# 从下到上,最多到 size,可以等于 size
while index <= self.size:
self.tree[index] += delta
index += self.__lowbit(index)
# 区间查询:查询小于等于 index 的元素个数
# 查询的语义是"前缀和"
def query(self, index):
res = 0
# 从上到下,最少到 1,可以等于 1
while index > 0:
res += self.tree[index]
index -= self.__lowbit(index)
return res
size = len(nums)
if size == 0: return []
if size == 1: return [0]
# 去重
s = list(set(nums))
len_s = len(s)
import heapq
heapq.heapify(s)
rank_map = dict()
rank = 1
for _ in range(len_s):
num = heapq.heappop(s)
rank_map[num] = rank
rank += 1
fenwick_tree = FenwickTree(len_s)
res = [None for _ in range(size)]
# 从后向前填表
for index in range(size - 1, -1, -1):
# 1、查询排名
rank = rank_map[nums[index]]
# 2、在树状数组排名的那个位置 + 1
fenwick_tree.update(rank, 1)
# 3、查询一下小于等于“当前排名 - 1”的元素有多少
res[index] = fenwick_tree.query(rank - 1)
return res
PS:
这两题也是可以用归并排序来解,在归并排序的合并过程中,会进行数字之间的比较,更好可以用来计数。目前只碰到了这两题与树状数组有关,以后要是刷到不一样的用法就再来更新。