当前位置: 首页 > article >正文

线段树(Segment Tree)和树状数组

线段树(Segment Tree)和树状数组

    • 线段树的实现
      • 链式:
      • 数组实现
  • 解题思路
    • 树状数组

线段树是 二叉树结构 的衍生,用于高效解决区间查询和动态修改的问题,其中区间查询的时间复杂度为 O(logN),动态修改单个元素的时间复杂度为x O(logN)。
这棵二叉树的叶子节点是数组中的元素,非叶子节点就是索引区间(线段)的汇总信息.
在这里插入图片描述
线段树结构可以有多种变体及复杂的优化,我们这里只聚焦最核心的两个 API:

class SegmentTree {// 构造函数,给定一个数组,初始化线段树,时间复杂度 O(N)// merge 是一个函数,用于定义 query 方法的行为// 通过修改这个函数,可以让 query 函数返回区间的元素和、最大值、最小值等public SegmentTree(int[] nums, Function<Integer, Integer> merge) {}// 查询闭区间 [i, j] 的元素和(也可能是最大最小值,取决于 merge 函数),时间复杂度 O(logN)public int query(int i, int j) {}// 更新 nums[i] = val,时间复杂度 O(logN)public void update(int i, int val) {}
}
  • suffixMin[i] 可以在 O(1) 时间内查询; nums[i…] 后缀的最小值线段树的 query 方法不仅可以查询后缀,还可以查询任意 [i, j] 区间,时间复杂度均为
    O(logN)。
  • 当底层 nums 数组中的任意元素变化时,需要重新计算 suffixMin 数组,时间复杂度为 O(N);而线段树的 update 方法可以在 O(logN) 时间内完成元素的修改。
  • 线段树不仅仅支持计算区间的最小值,只要修改 merge 函数,就可以支持计算区间元素和、最大值、乘积等。

线段树的实现

线段树是一种二叉树,所以可以用二叉树的方式实现,包括链式实现和数组实现两种:

链式:

from typing import Callable# 线段树节点
class SegmentNode:# 该节点表示的区间范围 [l, r]def __init__(self, merge_val: int, l: int, r: int):# [l, r] 区间元素的聚合值(如区间和、区间最大值等)self.l = lself.r = rself.merge_val = merge_valself.left = Noneself.right = Noneclass SegmentTree:def __init__(self, nums: list, merger: Callable[[int, int], int]):# 创建线段树# 输入数组 nums 和一个聚合函数 merger,merger 用于计算区间的聚合值self.merger = mergerself.root = self.build(nums, 0, len(nums) - 1)# 定义:将 nums[l..r] 中的元素构建成线段树,返回根节点def build(self, nums: list, l: int, r: int) -> SegmentNode:# 区间内只有一个元素,直接返回if l == r:return SegmentNode(nums[l], l, r)# 从中间切分,递归构建左右子树mid = l + (r - l) // 2left = self.build(nums, l, mid)right = self.build(nums, mid + 1, r)# 根据左右子树的聚合值,计算当前根节点的聚合值node = SegmentNode(self.merger(left.merge_val, right.merge_val), l, r)# 组装左右子树node.left = leftnode.right = rightreturn nodedef update(self, index: int, value: int):self._update(self.root, index, value)def _update(self, node: SegmentNode, index: int, value: int):if node.l == node.r:# 找到了目标叶子节点,更新值node.merge_val = valuereturnmid = node.l + (node.r - node.l) // 2if index <= mid:# 若 index 较小,则去左子树更新self._update(node.left, index, value)else:# 若 index 较大,则去右子树更新self._update(node.right, index, value)# 后序位置,左右子树已经更新完毕,更新当前节点的聚合值node.merge_val = self.merger(node.left.merge_val, node.right.merge_val)def query(self, qL: int, qR: int) -> int:return self._query(self.root, qL, qR)def _query(self, node: SegmentNode, qL: int, qR: int) -> int:if qL > qR:raise ValueError("Invalid query range")if node.l == qL and node.r == qR:# 命中了目标区间,直接返回return node.merge_val# 未直接命中区间,需要继续向下查找mid = node.l + (node.r - node.l) // 2if qR <= mid:# node.l <= qL <= qR <= mid# 目标区间完全在左子树中return self._query(node.left, qL, qR)elif qL > mid:# mid < qL <= qR <= node.r# 目标区间完全在右子树中return self._query(node.right, qL, qR)else:# node.l <= qL <= mid < qR <= node.r# 目标区间横跨左右子树# 将查询区间拆分成 [qL, mid] 和 [mid + 1, qR] 两部分,分别向左右子树查询# 最后将左右子树的查询结果合并return self.merger(self._query(node.left, qL, mid),self._query(node.right, mid + 1, qR))# Example usage
if __name__ == "__main__":arr = [1, 3, 5, 7, 9]# 示例,创建一棵求和线段树st = SegmentTree(arr, lambda a, b: a + b)print(st.query(1, 3)) # 3 + 5 + 7 = 15st.update(2, 10)print(st.query(1, 3)) # 3 + 10 + 7 = 20

数组实现

from typing import Callableclass ArraySegmentTree:# 用数组存储线段树结构def __init__(self, nums: list[int], merger: Callable[[int, int], int]):# 元素个数self.n = len(nums)self.merger = merger# 分配 4 倍数组长度的空间,存储线段树self.tree = [0] * (4 * self.n)self.build(nums, 0, self.n - 1, 0)# 定义:对 nums[l..r] 区间的元素构建线段树,rootIndex 是根节点def build(self, nums: list[int], l: int, r: int, rootIndex: int):if l == r:# 区间内只有一个元素,设置为叶子节点self.tree[rootIndex] = nums[l]return# 从中间切分,递归构建左右子树mid = l + (r - l) // 2leftRootIndex = self.leftChild(rootIndex)rightRootIndex = self.rightChild(rootIndex)# 递归构建 nums[l..mid],根节点为 leftRootIndexself.build(nums, l, mid, leftRootIndex)# 递归构建 nums[mid+1..r],根节点为 rightRootIndexself.build(nums, mid + 1, r, rightRootIndex)# 后序位置,左右子树已经构建完毕,更新当前节点的聚合值self.tree[rootIndex] = self.merger(self.tree[leftRootIndex], self.tree[rightRootIndex])def update(self, index: int, value: int):self._update(0, self.n - 1, 0, index, value)# 当前节点为 rootIndex,对应的区间为 [l, r]# 去子树更新 nums[index] 为 valuedef _update(self, l: int, r: int, rootIndex: int, index: int, value: int):if l == r:# 找到了目标叶子节点,更新值self.tree[rootIndex] = valuereturnmid = l + (r - l) // 2if index <= mid:# 若 index 较小,则去左子树更新self._update(l, mid, self.leftChild(rootIndex), index, value)else:# 若 index 较大,则去右子树更新self._update(mid + 1, r, self.rightChild(rootIndex), index, value)# 后序位置,左右子树已经更新完毕,更新当前节点的聚合值self.tree[rootIndex] = self.merger(self.tree[self.leftChild(rootIndex)],self.tree[self.rightChild(rootIndex)])def query(self, qL: int, qR: int) -> int:if qL < 0 or qR >= self.n or qL > qR:raise ValueError(f"Invalid range: [{qL}, {qR}]")return self._query(0, self.n - 1, 0, qL, qR)def _query(self, l: int, r: int, rootIndex: int, qL: int, qR: int) -> int:if qL == l and r == qR:# 命中了目标区间,直接返回return self.tree[rootIndex]mid = l + (r - l) // 2leftRootIndex = self.leftChild(rootIndex)rightRootIndex = self.rightChild(rootIndex)if qR <= mid:# node.l <= qL <= qR <= mid# 目标区间完全在左子树中return self._query(l, mid, leftRootIndex, qL, qR)elif qL > mid:# mid < qL <= qR <= node.r# 目标区间完全在右子树中return self._query(mid + 1, r, rightRootIndex, qL, qR)else:# node.l <= qL <= mid < qR <= node.r# 目标区间横跨左右子树# 将查询区间拆分成 [qL, mid] 和 [mid + 1, qR] 两部分,分别向左右子树查询return self.merger(self._query(l, mid, leftRootIndex, qL, mid),self._query(mid + 1, r, rightRootIndex, mid + 1, qR))def leftChild(self, pos: int) -> int:return 2 * pos + 1def rightChild(self, pos: int) -> int:return 2 * pos + 2if __name__ == "__main__":arr = [1, 3, 5, 7, 9]# 示例,创建一棵求和线段树st = ArraySegmentTree(arr, lambda a, b: a + b)print(st.query(1, 3)) # 3 + 5 + 7 = 15st.update(2, 10)print(st.query(1, 3)) # 3 + 10 + 7 = 20

307区域和检索

解题思路

针对不同的题目,我们有不同的方案可以选择(假设我们有一个数组):

1.数组不变,求区间和:「前缀和」、「树状数组」、「线段树」
2.多次修改某个数(单点),求区间和:「树状数组」、「线段树」
3.多次修改某个区间,输出最终结果:「差分」
4.多次修改某个区间,求区间和:「线段树」、「树状数组」(看修改区间范围大小)
5.多次将某个区间变成同一个数,求区间和:「线段树」、「树状数组」(看修改区间范围大小)
这样看来,「线段树」能解决的问题是最多的,那我们是不是无论什么情况都写「线段树」呢?

答案并不是,而且恰好相反,只有在我们遇到第 4 类问题,不得不写「线段树」的时候,我们才考虑线段树。

因为「线段树」代码很长,而且常数很大,实际表现不算很好。我们只有在不得不用的时候才考虑「线段树」。

总结一下,我们应该按这样的优先级进行考虑:

简单求区间和,用「前缀和」
多次将某个区间变成同一个数,用「线段树」
其他情况,用「树状数组」

作者:宫水三叶
链接:https://leetcode.cn/problems/range-sum-query-mutable/solutions/632515/guan-yu-ge-lei-qu-jian-he-wen-ti-ru-he-x-41hv/
来源:力扣(LeetCode)
著作权归作者所有。

树状数组

在这里插入图片描述
树状数组是用tree[i]维护从某个值开始到i的区间的元素和,下标从1开始
prefixsum[i]表示前i个数的前缀和,这样可以简单求[left,right]区间的元素和

sum[left,right] = prefixsum[right+1]-prefixsum[left]

prefixsum[i]被分解为几个小区间tree[i]的和,

#倒着分解,每次区间的长度为二进制的最低位bitlower 可以用`i&-i`计算
prefixsum[5] = [5,5]+[1,4] = tree[5]+tree[4]
prefixsum[11] = [11,11]+[9,10]+[1,8] = tree[11]+tree[10]+tree[8]
class NumArray:def __init__(self, nums: List[int]):n = len(nums)self.nums = [0]*nself.tree = [0]*(n+1) #初始化当成nums[i]每个元素从0更新到nums[i]for i,x in enumerate(nums):self.update(i,x)def update(self, index: int, val: int) -> None:delta = val-self.nums[index]self.nums[index]=vali = index+1while i<len(self.tree):self.tree[i]+=deltai+=i&-i  # 下一个包含nums[index]的数字def sumRange(self, left: int, right: int) -> int:return self.prefixsum(right+1)-self.prefixsum(left)def prefixsum(self,i):ans = 0while i:ans+=self.tree[i]i-= i&-i # return ans 
# Your NumArray object will be instantiated and called as such:
# obj = NumArray(nums)
# obj.update(index,val)
# param_2 = obj.sumRange(left,right)

相关文章:

线段树(Segment Tree)和树状数组

线段树&#xff08;Segment Tree&#xff09;和树状数组 线段树的实现链式&#xff1a;数组实现 解题思路树状数组 线段树是 二叉树结构 的衍生&#xff0c;用于高效解决区间查询和动态修改的问题&#xff0c;其中区间查询的时间复杂度为 O(logN)&#xff0c;动态修改单个元素的…...

MySQL注入中load_file()函数的使用

前言 在Msql注入中&#xff0c;load_file()函数在获得webshell以及提权过程中起着十分重要的作用&#xff0c;常被用来读取各种配置文件 而load_file函数只有在满足两个条件的情况下才可以使用&#xff1a; 文件权限&#xff1a;chmod ax pathtofile 文件大小&#xff1a;必须…...

[NOIP2007]矩阵取数游戏

点我写题 题目描述 帅帅经常跟同学玩一个矩阵取数游戏&#xff1a;对于一个给定的n*m的矩阵&#xff0c;矩阵中的每个元素aij均为非负整数。游戏规则如下&#xff1a; 1.每次取数时须从每行各取走一个元素&#xff0c;共n个。m次后取完矩阵所有元素&#xff1b; 2.每次取走的…...

DeepSeek-R1 论文解读 —— 强化学习大语言模型新时代来临?

近年来&#xff0c;人工智能&#xff08;AI&#xff09;领域发展迅猛&#xff0c;大语言模型&#xff08;LLMs&#xff09;为通用人工智能&#xff08;AGI&#xff09;的发展开辟了道路。OpenAI 的 o1 模型表现非凡&#xff0c;它引入的创新性推理时缩放技术显著提升了推理能力…...

使用Pygame制作“贪吃蛇”游戏

贪吃蛇 是一款经典的休闲小游戏&#xff1a;玩家通过操控一条会不断变长的“蛇”在屏幕中移动&#xff0c;去吃随机出现的食物&#xff0c;同时要避免撞到墙壁或自己身体的其他部分。由于其逻辑相对简单&#xff0c;但可玩性和扩展性都不错&#xff0c;非常适合作为新手练习游戏…...

云计算技术深度解析与实战案例

云计算技术深度解析与实战案例 引言 随着信息技术的飞速发展&#xff0c;云计算作为一种革命性的技术模式&#xff0c;已经渗透到各行各业&#xff0c;成为推动数字化转型的关键力量。本文旨在深入探讨云计算的技术特点、应用场景&#xff0c;并通过一个具体的代码使用案例&a…...

deb安装失败后,无法再安装别的包的解决方案

把package_name换成出安装问题的包 移除该包的安装标记 sudo dpkg --remove --force-remove-reinstreq package_name清理残留文件和配置 sudo apt-get purge package_name...

海外问卷调查如何影响企业的经营?在品牌建设中有何指导意义?

市场调查的定义&#xff1a;通过科学的方法&#xff0c;有目的地、系统地搜集整理一些市场信息&#xff0c;其目的在于了解当下市场现状和发展前景&#xff0c;为企业生产和品牌打造提供一些科学的指导意见&#xff0c;这是任何大企业、中小企业、初创企业都必须重视的一个重要…...

脚本运行禁止:npm 无法加载文件,因为在此系统上禁止运行脚本

问题与处理策略 1、问题描述 npm install -D tailwindcss执行上述指令&#xff0c;报如下错误 npm : 无法加载文件 D:\nodejs\npm.ps1&#xff0c;因为在此系统上禁止运行脚本。 有关详细信息&#xff0c;请参阅 https:/go.microsoft.com/fwlink/?LinkID135170 中的 about_…...

unity学习23:场景scene相关,场景信息,场景跳转

目录 1 默认场景和Assets里的场景 1.1 scene的作用 1.2 scene作为project的入口 1.3 默认场景 2 场景scene相关 2.1 创建scene 2.2 切换场景 2.3 build中的场景&#xff0c;在构建中包含的场景 &#xff08;否则会认为是失效的Scene&#xff09; 2.4 Scenes in Bui…...

CPU 100% 出现系统中断 怎么解决

CPU 100% 出现系统中断 怎么解决 电脑开机时会掉帧&#xff0c;切换到桌面时就会卡顿&#xff0c;然后打开任务管理器就会看到系统中断的cpu占用率达到100%&#xff0c;过一段时间再打开还是会有显示100%的占用率&#xff0c;这个问题怎么解决&#xff1f; 文章目录 CPU 100% …...

数据分析系列--⑥RapidMiner构建决策树(泰坦尼克号案例含数据)

一、资源下载 二、数据处理 1.导入数据 2.数据预处理 三、构建模型 1.构建决策树 2.划分训练集和测试集 3.应用模型 4.结果分析 一、资源下载 点击下载数据集 二、数据处理 1.导入数据 2.数据预处理 三、构建模型 1.构建决策树 虽然决策树已经构建,但对于大多数初学者或…...

【MyDB】4-VersionManager 之 4-VM的实现

【MyDB】4-VersionManager 之 4-VM的实现 VM 的实现VM(VersionManager)的基本定义与实现优化具体功能实现begin()开启事务commit()提交事务abort 中止事务read 读取uid对应的数据记录所在的entryinsert方法&#xff0c;插入数据delete方法 VM 的实现 本章代码位于&#xff1a;t…...

2024-2025自动驾驶技术演进与产业破局的深度实践——一名自动驾驶算法工程师的年度技术总结与行业洞察

一、引言&#xff1a;站在自动驾驶的"技术奇点" 2024年是自动驾驶行业从"技术验证"迈向"商业化落地"的关键转折点。从特斯拉FSD V12的端到端技术突破&#xff0c;到中国L3法规的破冰&#xff0c;从大模型重构感知架构&#xff0c;到城市NOA的&qu…...

计算机网络 笔记 传输层

概述&#xff1a; 主要功能&#xff1a; TCP&#xff1a; 特点***&#xff1a; 数据格式&#xff1a; 连接管理***&#xff1a; 建立连接&#xff08;三次握手&#xff09; 释放连接&#xff08;四次挥手&#xff09; 应用场景 UDP&#xff1a; 特点&#xff1a; 数…...

(leetcode 213 打家劫舍ii)

代码随想录&#xff1a; 将一个线性数组换成两个线性数组&#xff08;去掉头&#xff0c;去掉尾&#xff09; 分别求两个线性数组的最大值 最后求这两个数组的最大值 代码随想录视频 #include<iostream> #include<vector> #include<algorithm> //nums:2,…...

《TCP 网络编程实战:开发流程、缓冲区原理、三次握手与四次挥手》

一、 TCP 网络应用程序开发流程 学习目标 能够知道TCP客户端程序的开发流程1. TCP 网络应用程序开发流程的介绍 TCP 网络应用程序开发分为: TCP 客户端程序开发TCP 服务端程序开发说明: 客户端程序是指运行在用户设备上的程序 服务端程序是指运行在服务器设备上的程序,专门…...

62.异步编程+Prism

为什么不需要在构造函数中初始化了&#xff1f; private ICommand _fetchUserInfoCommand; public ICommand FetchUserInfoCommand > _fetchUserInfoCommand ?? new DelegateCommand(ExecuteFetchUserInfoAsync); public MainWindowViewModel() {// 无需…...

基于亿坊PHP框架构建物联网解决方案的优势分析!

在物联网 (IoT) 领域&#xff0c;选到合适的框架对于整个项目的开展也尤为重要。通常情况下&#xff0c;基于PHP的一些主流框架被用户常选择&#xff0c;今天就带大家了解下基于亿坊PHP框架构建物联网解决方案的优势有哪些&#xff1f; 1、开发效率高 在物联网项目中&#xf…...

把本地搭建的hexo博客部署到自己的服务器上

配置远程服务器的git 安装git 安装依赖工具包 yum install -y curl-devel expat-devel gettext-devel openssl-devel zlib-devel安装编译工具 yum install -y gcc perl-ExtUtils-MakeMaker package下载git&#xff0c;也可以去官网下载了传到服务器上 wget https://www.ke…...

《DeepSeek 实用集成:大模型能力接入各类软件》

DeepSeek 实用集成 awesome-deepseek-integration/README_cn.md at main deepseek-ai/awesome-deepseek-integration 将 DeepSeek 大模型能力轻松接入各类软件。访问 DeepSeek 开放平台来获取您的 API key。 English/简体中文 应用程序 Chatbox一个支持多种流行LLM模型的桌…...

接口使用实例(1)

大家好&#xff0c;今天我们来看看接口的一些实例&#xff0c;关于如何定义和实现接口&#xff0c;相信通过这些例子&#xff0c;我们能有一些清晰的认知。 先定义一个学生类&#xff1a; 再给定一个学生数组&#xff0c;对这个对象数组中的元素进行排序&#xff08;按分数排&…...

Git 版本控制:基础介绍与常用操作

目录 Git 的基本概念 Git 安装与配置 Git 常用命令与操作 1. 初始化本地仓库 2. 版本控制工作流程 3. 分支管理 4. 解决冲突 5. 回退和撤销 6. 查看提交日志 前言 在软件开发过程中&#xff0c;开发者常常需要在现有程序的基础上进行修改和扩展。但如果不加以管理&am…...

leetcode——合并K个有序链表(java)

给你一个链表数组&#xff0c;每个链表都已经按升序排列。 请你将所有链表合并到一个升序链表中&#xff0c;返回合并后的链表。 示例 1&#xff1a; 输入&#xff1a;lists [[1,4,5],[1,3,4],[2,6]] 输出&#xff1a;[1,1,2,3,4,4,5,6] 解释&#xff1a;链表数组如下&#…...

跨境数据传输问题常见解决方式

在全球化经济的浪潮下&#xff0c;跨境数据传输已然成为企业日常运营的关键环节。随着数字贸易的蓬勃发展和跨国业务的持续扩张&#xff0c;企业在跨境数据处理方面遭遇了诸多棘手难题。那么&#xff0c;面对这些常见问题&#xff0c;企业该如何应对&#xff1f;镭速跨境数据传…...

python-leetcode-删除链表的倒数第 N 个结点

19. 删除链表的倒数第 N 个结点 - 力扣&#xff08;LeetCode&#xff09; # Definition for singly-linked list. # class ListNode: # def __init__(self, val0, nextNone): # self.val val # self.next next class Solution:def removeNthFromEnd(self…...

EasyExcel写入和读取多个sheet

最近在工作中&#xff0c;作者频频接触到Excel处理&#xff0c;因此也对EasyExcel进行了一定的研究和学习&#xff0c;也曾困扰过如何处理多个sheet&#xff0c;因此此处分享给大家&#xff0c;希望能有所帮助 目录 1.依赖 2. Excel类 3.处理Excel读取和写入多个sheet 4. 执…...

lanqiaoOJ 2097:青蛙过河 ← 二分+前缀和+贪心

【题目来源】 https://www.lanqiao.cn/problems/2097/learning/ https://www.luogu.com.cn/problem/P8775 【题目描述】 小青蛙住在一条河边&#xff0c;它想到河对岸的学校去学习。小青蛙打算经过河里的石头跳到对岸。 备注&#xff1a;此图由百度 AI 创作生成 河里的石头排…...

woocommerce独立站与wordpress独立站的最大区别是什么

WooCommerce独立站与WordPress独立站的最大区别在于它们的功能定位和使用场景。 WordPress是一个开源的内容管理系统(CMS)&#xff0c;最初是作为博客平台发展起来的&#xff0c;但现在已经演变为一个功能丰富的网站构建工具。它主要用于创建动态网站&#xff0c;提供广泛的定…...

MybatisX插件快速创建项目

一、安装插件 二、创建一个数据表测试 三、IDEA连接Mysql数据库 四、选择MybatiX构造器 五、配置参数 六、项目结构...