树状数组(leetcode-315和剑指offer051)

树状数组(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(k1)。比如我要求原本数组中前六个的值的和,我们首先找到的便是树状数组中的节点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:

这两题也是可以用归并排序来解,在归并排序的合并过程中,会进行数字之间的比较,更好可以用来计数。目前只碰到了这两题与树状数组有关,以后要是刷到不一样的用法就再来更新。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值