LeetCode 307 Medium 区间和查询 分段树 Python

75 篇文章 0 订阅
72 篇文章 0 订阅

    算法:分段树 SegmentTree

    思路:

        将nums每次对半分段,构建分段树,分段树节点存储nums[left,right]的区间和

        分段树:

            线段树是一种平衡二叉搜索树(完全二叉树),它将一个线段区间划分成一些单元区 间。对于线段树中的每一个非叶子节

        点[a,b],它的左儿子表示的区间为[a,(a+b)/2],右 儿子表示的区间为[(a+b)/2+1,b],最后的叶子节点数目为N,与

        数组下标对应。线段树 的一般包括建立、查询、插入、更新等操作,建立规模为N的时间复杂度是 O(NlogN),其他操作时

        间复杂度为O(logN)。

            分段树是完全二叉树,可以用一维数组存储


class NumArray:
    def __init__(self, nums):
        """
        建立分段树:
            由于分段树最顶层的根节点存储的是[0,len(nums)]的区间和左子树存储的是[0,len(nums)/2]的区间和,
        右子树存储[len(nums)/2+1,len(nums)]区间和,以此类推,所以可以用递归的方式建立

                                [0,len(nums)-1]
                                 /           \
                        [0,len(nums)/2]     [len(nums)/2+1,len(nums)]
                               /              \
                            ....              ....
            如果用TreeNode的结构,TreeNode应该是如下形式:
                class SegmentTreeNode:
                    self.segment_sum = X
                    self.left_segment = i
                    self.right_segment = j
                    self.left_child = None
                    self.right_child = None
            由于这里用一维数组来实现,而上述SegmentTreeNode的内容又都必须包括,就需要在递归的过程中,计算并传递left,right
        而segemnt_sum则可以用values一维数组来存储,其中valuse的下标pos来表征当前节点与左孩子和右孩子的关系,
            当前节点的下标为pos,则:
                left_child at --> pos*2+1
                right_child at --> pos*2+2
            所以在建立过程中,如果left == right,则说明抵达了叶子节点,values[pos] = nums[left]
            否则的话,找到[left,right]的中间位置mid进行左右子树分割,左子树的区间段是[left,mid],右子树的区间段是[mid+1,right]
            计算出左子树和右子树的segment_sum后,当前pos位置的segment_sum值= values[left_pos]+values[right_pos]
        """
        if nums == []:
            return
        self.nums_end = len(nums) - 1
        self.values = [0] * 4 * len(nums) #小象学院PDF说一般线段树数组长度是原len(nums)的4倍

        def build_segment_tree(pos, left, right):
            if left == right:
                self.values[pos] = nums[left]
                return
            mid = (left + right) // 2
            build_segment_tree(pos * 2 + 1, left, mid)  # left child at pos*2+1
            build_segment_tree(pos * 2 + 2, mid + 1, right)  # right child at pos*2+2
            self.values[pos] = self.values[pos * 2 + 1] + self.values[pos * 2 + 2]

        build_segment_tree(0, 0, self.nums_end)

    def update(self, i, val):
        """
        更新分段树
            分段树虽然存储的是区间分段的和,但是分段树的叶子节点就是存储的nums中每个元素的值,对应于left==right的时候
            所以更新某个元素值时,递归地从下到上进行更新
             if left == right and left == i:
                更新pos的values = val
            否则求重点mid尽心分割,更新左右子树,
            values[pos] = 左子树和+右子树和
        """

        def update_segment_tree(pos, left, right):
            if left == right and left == i:
                self.values[pos] = val
                return
            mid = (left + right) // 2
            if i <= mid:
                update_segment_tree(pos * 2 + 1, left, mid)
            else:
                update_segment_tree(pos * 2 + 2, mid + 1, right)
            self.values[pos] = self.values[pos * 2 + 1] + self.values[pos * 2 + 2]
        update_segment_tree(0, 0, self.nums_end)

    def sumRange(self, i, j):
        """
        求[i,j]区间和
            当建立分段树后,求[i,j]区间和就比较顺畅了,将[i,j]从根节点向下遍历并分段,不断求得子区间的值并且sum起来即是答案
        在向下递归求和的过程中要注意递归的出口,其实也就是左右区间分段的原则,要注意在每次向下递归的时候,比较的是i,j与当前
        节点的left,right的关系来决定子分段和的值
            if [i,j] [left,right ] or [left,right] [i,j] i,j在left,right以外的位置
                return 0
            if [i,[lef,right],j] 如果left,right 在i,j内,
                return values[pos]  left,right内有多少返回多少
            否则的话说明 i,left,..,j,..right,ij与left,right是有部分有交集的,则对当前left,right区间通过中点mid进行分割
                return 左侧和+右侧和
        """

        def sum_range_segment_tree(pos, left, right):
            if i > right or j < left:
                return 0
            if left >= i and right <= j:  # 线段树当前范围[left,right]在所求区间[i,j]中则返回当前这个pos处的values
                return self.values[pos]
            mid = (left + right) // 2   #否则说明线段树当前pos的[left,right]的范围是d大于[i,j]的,需要分割
            return sum_range_segment_tree(pos * 2 + 1, left, mid) + sum_range_segment_tree(pos * 2 + 2, mid + 1, right)

        return sum_range_segment_tree(0, 0, self.nums_end)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值