leetcode|刷算法 线段树原理以及模板

线段树出现的题目特征

线段树使用的题目。每次操作都要得到返回结果的。

比如

699. 掉落的方块 - 力扣(LeetCode)

2286. 以组为单位订音乐会的门票 - 力扣(LeetCode)

1845. 座位预约管理系统 - 力扣(LeetCode)

线段树的原理

线段树的原理就是使用递归的思想对数值进行更新。

比如查询6到10区间上的最大值。如果传统的需要遍历一遍。但是如果有一个node刚好记录了这个范围6到10上的最大值,那么直接将这里的node上的数值返回即可。

另外,除了最大值,还有一个范围上的累加和,使得一个范围上进行更新操作,一个范围上进行累加操作。

原数组 orginArr

范围1到n上,需要使用4*(n+1) 的长度来记录树 使用arrSum表示

节点使用node标识,范围左边是left,右边是right

根节点就是 node=1 left=1 right=n

任意节点的左节点就是 node*2 左边边界 left 右边边界 right/2

任意节点的右节点就是 node*2+1 左边边界 right/2+1 右边边界 right

当左边界,右边界相同时候。left也就是1-n范围上的这个值对应的 arrSum[node] 就是表示原数组的该值origin[left]

线段树的模板

模板改自左程云老师的模板,属于二次改造,就高级写法进行降级,使得易懂。对单字母的变量进行多个字母命名,使得看着没有这么眼花

public static void main(String[] args) {int[] origin = {2, 1, 1, 2, 3, 4, 5};SegmentTree seg = new SegmentTree(origin);int start = 2;int end = 5;int value = 4;seg.add(start, end, value);  // start到end区间上统一增加值seg.update(start, end, value);  //  start到end区间上统一更新long sum = seg.querySum(start, end);  //  start到end区间求和}

这里入参会看着简洁些。start,end,value 也容易理解

另外的参数是left,right,node。

对于维护的数组,sumArr 树节点上的累加和 ,taskArr 树节点上的需要下发的增加数值的任务,changeArr 树节点上的需要下发的修改任务

taskArr[node] = 5 node的left和right。比如为6和10,也就是范围6到10上,都需要增加10

模板如下
public static void main(String[] args) {int[] origin = {2, 1, 1, 2, 3, 4, 5};SegmentTree seg = new SegmentTree(origin);int start = 2;int end = 5;int value = 4;seg.add(start, end, value);  // start到end区间上统一增加值seg.update(start, end, value);  //  start到end区间上统一更新long sum = seg.querySum(start, end);  //  start到end区间求和System.out.println(sum);System.out.println("对数器测试开始...");System.out.println("测试结果 : " + (test() ? "通过" : "未通过"));}// 线段树模板
public static class SegmentTree {int right;int left = 1;int[] arr;int[] sumArr;int[] taskArr;int[] changeArr;boolean[] updateArr;public SegmentTree(int[] origin) {right = origin.length;int length = right + 1;arr = new int[length];for (int i = 1; i < length; i++) {arr[i] = origin[i - 1];}sumArr = new int[length * 4];taskArr = new int[length * 4];// changeArr和updateArr是一起使用的,但是一般题目可以不需要updateArr,因为通常修改的数值是大于0的。可以用是否为0来判断changeArr = new int[length * 4];updateArr = new boolean[length * 4];// 构建线段树的 SumArrbuildSumArr(left, right, 1);}public void buildSumArr(int left, int right, int node) {if (left == right) { // 一定要拆解到单个节点的时候才可以进行赋值sumArr[node] = arr[left];return;}int mid = (left + right) / 2;buildSumArr(left, mid, node * 2); // 左节点buildSumArr(mid + 1, right, node * 2 + 1); // 右节点// 求左右节点的和sumArr[node] = sumArr[node * 2] + sumArr[node * 2 + 1];}private void refresh(int root, int leftNum, int rightNum) {// update和change必须是一起出现的// task是单独出现的,而且上面操作的会将task给覆盖掉if (updateArr[root]) {  // 它以及下面的树需要更新updateArr[root * 2] = true;updateArr[root * 2 + 1] = true;changeArr[root * 2] = changeArr[root]; // 需要更新的值changeArr[root * 2 + 1] = changeArr[root]; // 需要更新的值taskArr[root * 2] = 0; // 既然都更新了,那么lazy的值就可以清空了taskArr[root * 2 + 1] = 0;sumArr[root * 2] = changeArr[root] * leftNum; // 更新求和信息sumArr[root * 2 + 1] = changeArr[root] * rightNum; // 更新求和信息updateArr[root] = false;  // 自身更新好了}if (taskArr[root] != 0) {     // 这个值是需要下发的值taskArr[root * 2] += taskArr[root]; // 自身需要下发的值,加上来自上面的值sumArr[root * 2] += taskArr[root] * leftNum; // 结算上面下发的值taskArr[root * 2 + 1] += taskArr[root];sumArr[root * 2 + 1] += taskArr[root] * rightNum;  // 同上taskArr[root] = 0;  // 需要下发的值更新}}public void update(int start, int end, int value) {// 这里的left,right,1非常重要,1代表的是节点的位置,而left和right代表的是改节点的管辖范围。这里是root开始,root为1// int left, int right, int node  代表这个节点的范围,每次会更新传递// 这里的left和right最终会聚合到一起,代表的是arr的位置update(start, end, value, left, right, 1);}public void update(int start, int end, int value, int left, int right, int node) {// 满足更新的条件if (start <= left && right <= end) {updateArr[node] = true;  // 它以及下面的树需要更新changeArr[node] = value;  //更新数字sumArr[node] = value * (right - left + 1); // 求和taskArr[node] = 0;return;}// 任务下发。int mid = (left + right) / 2;// 左树的个数,和右树的个数refresh(node, mid - left + 1, right - mid);if (start <= mid) {update(start, end, value, left, mid, node * 2);}if (end > mid) {update(start, end, value, mid + 1, right, node * 2 + 1);}sumArr[node] = sumArr[node * 2] + sumArr[node * 2 + 1];}// 添加public void add(int start, int end, int value) {add(start, end, value, left, right, 1);}public void add(int start, int end, int value, int left, int right, int node) {// 开始点和结束点,把树的左右范围给包括了if (start <= left && right <= end) {sumArr[node] += value * (right - left + 1); // 这里的结算逻辑非常重要,会根据不同的题目进行变换taskArr[node] += value;return;}int mid = (left + right) / 2;// 这里最要是对于changeArr和taskArr的操作refresh(node, mid - left + 1, right - mid);if (start <= mid) { // 如果开始位置比mid大,那么左边就没有下发的意义了,只要下发右边就行了add(start, end, value, left, mid, node * 2); // 左节点 这里和初始化的策略一样}if (end > mid) {add(start, end, value, mid + 1, right, node * 2 + 1); // 右节点}sumArr[node] = sumArr[node * 2] + sumArr[node * 2 + 1];}public long querySum(int start, int end) {return querySum(start, end, left, right, 1);}public long querySum(int start, int end, int left, int right, int node) {// 这里是返回当前节点,因为start和end已经被包围了。if (start <= left && right <= end) {return sumArr[node];}int mid = (left + right) / 2;refresh(node, mid - left + 1, right - mid);long ans = 0;if (start <= mid) { // 下发左边的ans += querySum(start, end, left, mid, node * 2);}if (end > mid) { // 下发右边的ans += querySum(start, end, mid + 1, right, node * 2 + 1);}return ans;}}
//=============== 下面是用于对比测试的代码public static class Right {public int[] arr;public Right(int[] origin) {arr = new int[origin.length + 1];for (int i = 0; i < origin.length; i++) {arr[i + 1] = origin[i];}}public void update(int L, int R, int C) {for (int i = L; i <= R; i++) {arr[i] = C;}}public void add(int L, int R, int C) {for (int i = L; i <= R; i++) {arr[i] += C;}}public long query(int L, int R) {long ans = 0;for (int i = L; i <= R; i++) {ans += arr[i];}return ans;}}public static int[] genarateRandomArray(int len, int max) {int size = (int) (Math.random() * len) + 1;int[] origin = new int[size];for (int i = 0; i < size; i++) {origin[i] = (int) (Math.random() * max) - (int) (Math.random() * max);}return origin;}public static boolean test() {int len = 100;int max = 1000;int testTimes = 5000;int addOrUpdateTimes = 1000;int queryTimes = 500;for (int i = 0; i < testTimes; i++) {int[] origin = genarateRandomArray(len, max);SegmentTree seg = new SegmentTree(origin);int N = origin.length;Right rig = new Right(origin);for (int j = 0; j < addOrUpdateTimes; j++) {int num1 = (int) (Math.random() * N) + 1;int num2 = (int) (Math.random() * N) + 1;int L = Math.min(num1, num2);int R = Math.max(num1, num2);int C = (int) (Math.random() * max) - (int) (Math.random() * max);if (Math.random() < 0.5) {seg.add(L, R, C);rig.add(L, R, C);} else {seg.update(L, R, C);rig.update(L, R, C);}}for (int k = 0; k < queryTimes; k++) {int num1 = (int) (Math.random() * N) + 1;int num2 = (int) (Math.random() * N) + 1;int L = Math.min(num1, num2);int R = Math.max(num1, num2);long ans1 = seg.querySum(L, R);long ans2 = rig.query(L, R);if (ans1 != ans2) {System.out.println(k);System.out.println(ans1 + "   " + ans2);return false;}}}return true;}
总结

个人练习了3道线段树的题目。写出来非常不容易,带着模板都没这么简单可以做出来

更多的是要学会这里递归的思想,做到灵活运用。像是2286题目 和区间没有关系,每次都是需要细化到单点。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/54056.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【PHP陪玩系统源码】游戏陪玩系统app,陪玩小程序优势

陪玩系统开发运营级别陪玩成品搭建 支持二开源码交付&#xff0c;游戏开黑陪玩系统: 多客陪玩系统&#xff0c;游戏开黑陪玩&#xff0c;线下搭子&#xff0c;开黑陪玩系统 前端uniapp后端php&#xff0c;数据库MySQL 1、长时间的陪玩APP源码开发经验&#xff0c;始终坚持从客户…

Docker镜像命令和容器命令

目录 镜像命令 镜像命名规范 镜像操作命令 DockerHub拉取镜像 利用docker save将nginx镜像导出磁盘&#xff0c;然后再通过load加载回来 总结 容器命令介绍和案例 容器相关命令 案例&#xff1a;创建运行一个Nginx容器 总结 镜像命令 镜像命名规范 镜像名称一般分两…

uniapp框架中实现文件选择上传组件,可以选择图片、视频等任意文件并上传到当前绑定的服务空间

前言 uni-file-picker是uniapp中的一个文件选择器组件,用于选择本地文件并返回选择的文件路径或文件信息。该组件支持选择单个文件或多个文件,可以设置文件的类型、大小限制,并且可以进行文件预览。 提示:以下是本篇文章正文内容,下面案例可供参考 uni-file-picker组件具…

了解华为计算产品线,昇腾的业务都有哪些?

&#x1f349; CSDN 叶庭云&#xff1a;https://yetingyun.blog.csdn.net/ 随着 ChatGPT 的现象级爆红&#xff0c;它引领了 AI 大模型时代的深刻变革&#xff0c;进而造成 AI 算力资源日益紧缺。与此同时&#xff0c;中美贸易战的持续也使得 AI 算力国产化适配成为必然趋势。 …

Temporal Dynamic Quantization for Diffusion Models阅读

文章目录 AbstractIntroductionBackgrounds and Related Works2.1 扩散模型2.2 量化2.3 量化感知训练和训练后量化 TemporalDynamic Quantization3.1 量化方法3.2 扩散模型量化的挑战3.3 TDQ模块的实现3.4 工程细节时间步的频率编码TDQ模块的初始化 Experimental SetupResults5…

基于SpringBoot+Vue+MySQL的美食信息推荐系统

系统展示 用户前台界面 管理员后台界面 系统背景 在数字化时代&#xff0c;随着人们对美食文化的热爱与追求不断增长&#xff0c;美食信息推荐系统成为了连接食客与美食之间的重要桥梁。面对海量的美食信息&#xff0c;用户往往难以快速找到符合个人口味和需求的美食。因此&…

实用工具推荐---- PDF 转换

直接上链接&#xff1a;爱PDF |面向 PDF 爱好者的在线 PDF 工具 (ilovepdf.com) 主要功能如下&#xff1a; 全免费&#xff01;&#xff01;&#xff01;&#xff01;

FPGA远程烧录bit流

FPGA远程烧录bit流 Vivado支持远程编译并下载bit流到本地xilinx开发板。具体操作就是在连接JTAG的远程电脑上安装hw_server.exe。比如硬件板在实验室或者是其他地方&#xff0c;开发代码与工程在本地计算机&#xff0c;如何将bit流烧录到实验室或者远程开发板&#xff1f; vi…

机器学习-TopicModel

概率图模型基础概率潜在语义分析&#xff08;PLSA&#xff09;LDA 概率图模型基础 猜球游戏 有两个信封&#xff0c;其中一个装有一个红球&#xff0c;一个黑球。另一个信封有两个黑球。 。 假设红球价值100元&#xff0c;黑球价值1元。 你随机从其中拿起一个信封&#xff0c;从…

STM32 OLED

文章目录 前言一、OLED是什么&#xff1f;二、使用步骤1.复制 OLED.C .H文件1.1 遇到问题 2.统一风格3.主函数引用头文件3.1 oled.h 提供了什么函数 4.介绍显示一个字符的函数5. 显示十进制函数的讲解 三、使用注意事项3.1 配置符合自己的引脚3.2 花屏总结 前言 提示&#xff…

简单vue指令实现 el-table 可拖拽表格功能

安装 SortableJS sorttableJs 相关优点如下&#xff1a; 相关配置项 参考 &#x1f449; SortableJS中文官网 pnpm i sortablejs封装成指令 不多逼逼&#xff0c;直接上才艺 &#x1f92a;&#x1f92a;&#x1f92a; 先安装一个 nanoid 插件 用于生成随机id&#xff0c;注…

亚洲市场|人工智能对固态硬盘SSD需求影响

随着人工智能(AI)技术的快速发展&#xff0c;对于高效能存储的需求也在日益增长。在亚洲市场中&#xff0c;固态硬盘(SSD)作为关键的数据存储设备&#xff0c;其重要性不言而喻。 扩展阅读&#xff1a; 内存&#xff1a;生成式AI带来全新挑战与机遇 这可能是最清晰的AI存储数…

瑞芯微RK3566鸿蒙开发板Android11修改第三方输入法为默认输入法

本文适用于触觉智能所有支持Android11系统的开发板修改第三方输入法为默认输入法。本次使用的是触觉智能的Purple Pi OH鸿蒙开源主板&#xff0c;搭载了瑞芯微RK3566芯片&#xff0c;类树莓派设计&#xff0c;是Laval官方社区主荐的一款鸿蒙开发主板。 一、安装输入法并查看输入…

YOLOv8改进 | 主干篇,YOLOv8改进主干网络为华为的轻量化架构GhostNetV1

摘要 摘要:将卷积神经网络(CNN)部署在嵌入式设备上是困难的,因为嵌入式设备的内存和计算资源有限。特征图的冗余是成功的 CNN 的一个重要特征,但在神经网络架构设计中很少被研究。作者提出了一种新颖的 Ghost 模块,用于通过廉价操作生成更多的特征图。基于一组内在特征图…

使用Python和Proxy302代理IP高效采集Bing图片

目录 项目背景一、项目准备环境配置 二、爬虫设计与实现爬虫设计思路目标网站分析数据获取流程 代码实现1. 初始化爬虫类&#xff08;BingImageSpider&#xff09;2. 创建存储文件夹3. 获取图像链接4. 下载图片5. 使用Proxy302代理IP6. 主运行函数 运行截图 三、总结 项目背景 …

VS开发 - 静态编译和动态编译的基础实践与混用

目录 1. 基础概念 2. 直观感受一下静态编译和动态编译的体积与依赖项目 3. VS运行时库包含哪些主要文件&#xff08;从VS2015起&#xff09; 4. 动态库和静态库混用的情况 5. 感谢清单 1. 基础概念 所谓的运行时库&#xff08;Runtime Library&#xff09;就是WINDOWS系统…

WPS在表格中填写材料时,内容过多导致表格不换页,其余内容无法正常显示 以及 内容过多,导致表格换页——解决方法

一、现象 1&#xff0c;内容过多导致表格不换页&#xff0c;其余内容无法正常显示 2&#xff0c;内容过多&#xff0c;导致表格换页 二、解决方法 在表格内右击&#xff0c;选择表格属性 在菜单栏选择行&#xff0c;勾选允许跨页断行&#xff0c;点击确定即可 1&#xff0…

【WRF工具】WRF Domain Wizard第二期:服务器中下载及安装

【WRF工具】WRF Domain Wizard第二期&#xff1a;服务器下载及安装 准备WRF Domain Wizard下载及安装WRF Domain Wizard下载WRF Domain Wizard安装添加环境变量&#xff08;为当前用户永久添加环境变量&#xff09;Java环境安装报错-Exception in thread "main" java…

STM32F1+HAL库+FreeTOTS学习15——互斥信号量

STM32F1HAL库FreeTOTS学习15——互斥信号量 1. 优先级翻转2. 互斥信号量3. 相关API函数&#xff1b;3.1 互斥信号量创建3.2 获取信号量3.3 释放信号量3.4 删除信号量 4. 操作实验1. 实验内容2. 代码实现3. 运行结果 上期我们介绍了数值信号量。这一期我们来介绍互斥信号量 1. 优…

[Docker学习笔记]利用Dockerfile创建镜像

Dockerfile 指令 指令作用from继承基础镜像maintainer镜像制作者信息(可缺省)run用来执行shell命令expose暴露端口号cmd启动容器默认执行的命令entrypoint启动容器真正执行的命令volume创建挂载点env配置环境变量add复制文件到容器copy复制文件到容器workdir设置容器的工作目录…