0基础学习线段树

news2024/11/25 2:30:44

前言:

线段树:用树来表示一个一个的线段区间。

1、为什么要使用线段树?

题目:给定一个数组nums,我们有两种下面两种操作

1、查询nums数组下标i到下标j的和;

2、将nums数组指定下标的值改为指定的一个新值;

如果上面两种操作频繁交叉进行,如何使整体效率更高。

方案一:更新操作O1

每次查询操作都从i加到j,每次更新直接更新对应下标。

缺点:如果每次查询操作都是查整个数组的和,一下子来了几万个查询操作,则每次都是O(n),几万次O(n),比较浪费时间。

方案二:查询操作O1

新建一个同等大小数组dp,dp【i】用于记录从nums[0]加到nums[i]的值。

缺点:虽然查询操作从O(n),变成O1(dp[j]-dp[i-1])。但是更新操作需要变成了O(n),每次更新都需要维护dp数组,从更新处开始往后面的值都需要重新计算,如果一次性来了大量的更新操作请求,则比较浪费时间。

方案三:线段树(查询Ologn,更新Ologn)

2、线段树为什么开4n空间?

1、假设n刚好是2的整数次阶乘,则n的满二叉树具有下面特征:

1)树的层高:h=log2^n+1;

2)树的最后一层的节点数:an=2^(h-1);

3)树的节点数总和:sn=2n-1;

那为什么要开4n空间呢?因为如果n=9,呢?即n不是刚好2的某个整数的阶乘,这时候会在满二叉树的基础上多出一层来。

多出来一层的节点数为:an+1=2^h;

那么树的节点数总和就变成了:sn=2n-1+2^h,h=log2^n+1,所以:sn=4n-1。

因为存储的时候下标从1开始,所以我们给4n的空间。

3、构造线段树

1、节点数组下标分配:从上到下,从左到右

2、父子节点间下标关系:

l = fa*2 (左子树下标为父节点下标的两倍)
r = fa*2+1(右子树下标为父节点下标的两倍+1)

3、代码如下:

class Tree{
	int[] nums;
	int[] tree;
	public Tree(int[] nums){
		this.nums = nums;
		int n = nums.length;
		tree = new int[4*n];
		build(nums,0,0,0);
		
	}
//	l = fa*2 (左子树下标为父节点下标的两倍)
//	r = fa*2+1(右子树下标为父节点下标的两倍+1)	
	public void build(int[] nums,int id,int l,int r){
		if(l==r){
			tree[id] = nums[l];
			return;
		}
		int mid = (l + r)/2;
		build(nums,2*id,l,mid);
		build(nums,2*id+1,mid,r);
		tree[id] = tree[2*id]+tree[2*id+1];			
	}
}

4、线段树区间查询

我们知道线段树的每个结点存储的都是一段区间的信息 ,如果我们刚好要查询这个区间,那么则直接返回这个结点的信息即可,比如对于上面线段树,如果我直接查询[1,6]这个区间的最值,那么直接返回根节点信息返回13即可,但是一般我们不会凑巧刚好查询那些区间,比如现在我要查询[2,5]区间的最值,这时候该怎么办呢,我们来看看哪些区间被[2,5]包含了。

一共有5个区间,而且我们可以发现[4,5]这个区间已经包含了两个子树的信息([4,4],[5,5]),所以我们需要查询的区间只有三个,分别是[2,2],[3,3],[4,5],我们从根节点开始往下递归,如果当前结点是被要查询的区间包含了的,则返回这个结点的信息,这样从根节点往下递归,时间复杂度也是O(logN)。

————————————————

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
                        
原文链接:https://blog.csdn.net/weq2011/article/details/128791426

//	查询指定区间内的值的和
	public int find(int id,int l,int r,int x,int y){
//		当区间完全在要求查找区间范围内时,就是我们要的值
		if(l>=x && r<=y){
			return tree[id];
		}
		int mid = (l + r)/2;
		int sum = 0;
//		如果值存在于左子树,则需要进行查找
		if(x<=mid){
			sum+=find(2*id,l,mid,x,y);
		}
//		如果值存在于右子树,则需要进行查找
		if(y>mid){
			sum+=find(2*id+1,mid+1,r,x,y);
		}
		return sum;
	}

5、线段树更新

id:从线段树哪个下标开始检索

l:数值区间左

r:数值区间右

index:需要更新的dp下标

val:更新的值

//	更新指定下标的值
//	指定下标
	public void update(int id,int l,int r,int index,int val){
		tree[id] = tree[id]+val;
		if(l == index && r == index){
			return;
		}
		int mid = (l + r)/2;
		if(index<=mid){
			update(2*id,l,mid,index,val);
		}
		if(index>mid){
			update(2*id+1,mid+1,r,index,val);
		}
		
	}

6、完整案例及代码

3187. 数组中的峰值

数组 arr 中 大于 前面和后面相邻元素的元素被称为 峰值 元素。

给你一个整数数组 nums 和一个二维整数数组 queries 。

你需要处理以下两种类型的操作:

  • queries[i] = [1, li, ri] ,求出子数组 nums[li..ri] 中 峰值 元素的数目。
  • queries[i] = [2, indexi, vali] ,将 nums[indexi] 变为 vali 。

请你返回一个数组 answer ,它依次包含每一个第一种操作的答案。

注意:

  • 子数组中 第一个 和 最后一个 元素都 不是 峰值元素。

 

示例 1:

输入:nums = [3,1,4,2,5], queries = [[2,3,4],[1,0,4]]

输出:[0]

解释:

第一个操作:我们将 nums[3] 变为 4 ,nums 变为 [3,1,4,4,5] 。

第二个操作:[3,1,4,4,5] 中峰值元素的数目为 0 。

示例 2:

输入:nums = [4,1,4,2,1,5], queries = [[2,2,4],[1,0,2],[1,0,4]]

输出:[0,1]

解释:

第一个操作:nums[2] 变为 4 ,它已经是 4 了,所以保持不变。

第二个操作:[4,1,4] 中峰值元素的数目为 0 。

第三个操作:第二个 4 是 [4,1,4,2,1] 中的峰值元素。 

提示:

  • 3 <= nums.length <= 105
  • 1 <= nums[i] <= 105
  • 1 <= queries.length <= 105
  • queries[i][0] == 1 或者 queries[i][0] == 2
  • 对于所有的 i ,都有:
    • queries[i][0] == 1 :0 <= queries[i][1] <= queries[i][2] <= nums.length - 1
    • queries[i][0] == 2 :0 <= queries[i][1] <= nums.length - 11 <= queries[i][2] <= 105

1)线段树求解代码: 

package algorithm.temp;

import java.util.ArrayList;
import java.util.List;

class Test2 {
	static final int COUNT_PEAK = 1, UPDATE = 2;

	public static void main(String[] args) {
		int[][] queyy = new int[4][3];
		queyy[0][0] = 2;
		queyy[0][1] = 0;
		queyy[0][2] = 2;
		queyy[1][0] = 1;
		queyy[1][1] = 0;
		queyy[1][2] = 3;
		
//		queyy[0][0] = 1;
//		queyy[0][1] = 2;
//		queyy[0][2] = 4;
//		queyy[1][0] = 1;
//		queyy[1][1] = 0;
//		queyy[1][2] = 1;
		queyy[2][0] = 1;
		queyy[2][1] = 3;
		queyy[2][2] = 3;
		queyy[3][0] = 2;
		queyy[3][1] = 3;
		queyy[3][2] = 5;
		System.out.println(countOfPeaks(new int[] {9,7,5,8,9}, queyy));
		;
	}
	    public static List<Integer> countOfPeaks(int[] nums, int[][] queries) {
	        List<Integer> li = new ArrayList<Integer>();
	        int[] dp = new int[nums.length];
	        dp[0] = 0;
	        for(int i=1;i<nums.length;i++){
	            if(isTopElement(nums,i)){
	                dp[i]= 1;
	                dp[++i] = 0;
	            };
	        }
	        Tree tree = new Tree(dp);
	        
	        for(int i=0;i<queries.length;i++){
	            int[] temp = queries[i];
	            if(temp[0] == 1){
	                int x = temp[1];
	                int y = temp[2];
	                li.add(y-x>1?tree.find(1, 0, dp.length-1, x+1, y-1):0);
	            }else{
	            	int index = temp[1];
	            	int val = temp[2];
	            	nums[index] = val;
	            	for(int k=-1;k<=1;k++){
	            		int num=0;
	            		if(index+k>0 && index+k<nums.length-1){
	            			num=isTopElement(nums,index+k)?1-dp[index+k]:0-dp[index+k];
	            			tree.update(1,0, dp.length-1, index+k, num);
	            			dp[index+k] = dp[index+k]+num;
	            		}
	            	}
	            }
	        }
	        return li;
	    }

	    public static boolean isTopElement(int[] nums,int index) {
	        if(index-1>=0 && index+1<=nums.length-1 && nums[index]>nums[index-1] && nums[index]>nums[index+1]){
	            return true;
	        }
	        return false;
	    }
	}

class Tree{
	int[] nums;
	int[] tree;
	public Tree(int[] nums){
		this.nums = nums;
		int n = nums.length;
		tree = new int[4*n];
		build(nums,1,0,n-1);
		
	}
//	l = fa*2 (左子树下标为父节点下标的两倍)
//	r = fa*2+1(右子树下标为父节点下标的两倍+1)	
	public void build(int[] nums,int id,int l,int r){
		if(l==r){
			tree[id] = nums[l];
			return;
		}
		int mid = (l + r)/2;
		build(nums,2*id,l,mid);
		build(nums,2*id+1,mid+1,r);
		tree[id] = tree[2*id]+tree[2*id+1];			
	}
//	查询指定区间内的值的和
	public int find(int id,int l,int r,int x,int y){
//		当区间完全在要求查找区间范围内时,就是我们要的值
		if(l>=x && r<=y){
			return tree[id];
		}
		int mid = (l + r)/2;
		int sum = 0;
//		如果值存在于左子树,则需要进行查找
		if(x<=mid){
			sum+=find(2*id,l,mid,x,y);
		}
//		如果值存在于右子树,则需要进行查找
		if(y>mid){
			sum+=find(2*id+1,mid+1,r,x,y);
		}
		return sum;
	}
//	更新指定下标的值
//	指定下标
	public void update(int id,int l,int r,int index,int val){
		tree[id] = tree[id]+val;
		if(l == index && r == index){
			return;
		}
		int mid = (l + r)/2;
		if(index<=mid){
			update(2*id,l,mid,index,val);
		}
		if(index>mid){
			update(2*id+1,mid+1,r,index,val);
		}
		
	}
}

2)暴力求解代码

(执行效率低下,本人第一次就是写的这段代码,案例跑超时):

class Solution {
    public List<Integer> countOfPeaks(int[] nums, int[][] queries) {
        List<Integer> li = new ArrayList<Integer>();
        int[] dp = new int[nums.length];
        dp[0] = 0;
        for(int i=1;i<nums.length;i++){
            if(isTopElement(nums,i)){
                dp[i]= 1;
                dp[++i] = 0;
            };
        }

        for(int i=0;i<queries.length;i++){
            int[] temp = queries[i];
            if(temp[0] == 1){
                int sum = 0;
                for(int k=temp[1]+1;k<temp[2]&&k<nums.length;k++){
                    sum+=dp[k];
                }
                li.add(sum);
            }else{
            	nums[temp[1]] = temp[2];
                for(int k=temp[1]-1;k<=temp[1]+1;k++){
                    if(isTopElement(nums,k)){
                    	dp[k] = 1;
                    }else if(k>0&&k<nums.length){
                    	dp[k] = 0;
                    }
                }
            }
        }
        return li;
    }

    public boolean isTopElement(int[] nums,int index) {
        if(index-1>=0 && index+1<=nums.length-1 && nums[index]>nums[index-1] && nums[index]>nums[index+1]){
            return true;
        }
        return false;
    }
}

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

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

相关文章

screenshot-to-code之安装、测试

准备 GPT收费账号 screenshot-to-code Supported AI models: GPT-4O - Best model!GPT-4 Turbo (Apr 2024)GPT-4 Vision (Nov 2023)Claude 3 SonnetDALL-E 3 for image generation git或者手动 下载源代码 https://github.com/abi/screenshot-to-code pip install poetry (…

【图像识别系统】昆虫识别Python+卷积神经网络算法+人工智能+深度学习+机器学习+TensorFlow+ResNet50

一、介绍 昆虫识别系统&#xff0c;使用Python作为主要开发语言。通过TensorFlow搭建ResNet50卷积神经网络算法&#xff08;CNN&#xff09;模型。通过对10种常见的昆虫图片数据集&#xff08;‘蜜蜂’, ‘甲虫’, ‘蝴蝶’, ‘蝉’, ‘蜻蜓’, ‘蚱蜢’, ‘蛾’, ‘蝎子’, ‘…

ROS话题通信机制实操C++

ROS话题通信机制实操C 创建ROS工程发布方(二狗子)订阅方(翠花)编辑配置文件编译并执行注意订阅的第一条数据丢失 ROS话题通信的理论查阅ROS话题通信流程理论 在ROS话题通信机制实现中&#xff0c;ROS master 不需要实现&#xff0c;且连接的建立也已经被封装了&#xff0c;需要…

反馈时延与端到端拥塞控制

先从 越来越无效的拥塞控制 获得一个直感。 开局一张图&#xff0c;剩下全靠编。这是一道习题&#xff1a; 这图来自《高性能通信网络(第二版)》&#xff0c;2002 年的书&#xff0c;很好很高尚&#xff0c;目前这种书不多了。不准备做这道题&#xff0c;但意思要明白&#x…

Redis学习|Redis基础知识、Redis五大数据类型、Redis三种特殊数据类型、Redis事务

Redis基础知识 redis默认有16个数据库&#xff0c;并且这个数量可以在conf配置文件中更改 默认使用的是第0个 可以使用 select 进行切换数据库! key *查看数据库所有的key 清除当前数据库 flushdb 清除全部数据库的内容FLUSHALL 为什么redis是6379!(了解一下即可!) Redis 是…

关于笔记本电脑连接电源时触摸板失灵、卡顿、乱飘的问题

目录 前言 问题原因 解决方法 前言 我查阅了相关的资料和方法如下&#xff08;很感谢这位楼主大佬提供的问题所在&#xff09;&#xff1a; 问题原因 解决方法 那么解决方法无非就是几种&#xff08;方法仅供参考&#xff0c;不排除一些危险性&#xff09;&#xff1a; 1…

微信公众号 H5授权登录实现(最详细)

一、微信公众号 &#xff08;一&#xff09;基础信息 微信授权类型 自己的网站、APP等第三方&#xff0c;要实现接入微信授权登录&#xff0c;有多种方式&#xff1a;微信公众号&#xff08;网页&#xff09;、微信小程序、微信开放平台&#xff08;APP&#xff09;等等。 【…

什么是标准差和方差

标准差是用于衡量数字是如何分布的指标。用σ &#xff08;sigma&#xff09;表示。 标准差方差的平方根。 什么是方差 方差就是与均值的平方差的平均值。方差的计算过程&#xff1a; 计算平均值&#xff08;mean&#xff09;用μ /读mu/表示。用每一个数减去平均值&#xf…

Java基础的重点知识-04-封装

文章目录 面向对象思想封装 面向对象思想 在计算机程序设计过程中&#xff0c;参照现实中事物&#xff0c;将事物的属性特征、行为特征抽象出来&#xff0c;描述成计算机事件的设计思想。 面向对象思想的三大基本特征: 封装、继承、多态 1.类和对象 类是对象的抽象&#xff…

FreeCAD中类型机制研究

了解FreeCAD类型机制实现原理&#xff0c;为后续FreeCAD相关工作提供参考。 1.实现原理 FreeCAD系统提供一个最上层的基类BaseClass&#xff0c;该类主要处理类型相关工作&#xff0c;几乎所有的FreeCAD的类直接或间接继承于该类。该类只有唯一个属性Type&#xff0c;Type里面…

如何提升外链网站的收录率?

要提高外链网站的收录率&#xff0c;要明确的一点是&#xff0c;被收录的外链才能发挥最大的作用&#xff0c;因此&#xff0c;提升收录率是首要任务。一个有效的方法是使用GPC爬虫池&#xff0c;这样可以大幅度提高谷歌蜘蛛对众多外链网站页面的抓取频率 通过GPC爬虫池的引导…

OpenSSL命令手册

正文共&#xff1a;999 字 10 图&#xff0c;预估阅读时间&#xff1a;1 分钟 我们前面编译安装了OpenSSL命令工具&#xff08;CentOS编译安装OpenSSL 3.3.1&#xff09;&#xff0c;这是一个强大的安全套接字层密码库&#xff0c;可以用于实现各种加密和认证协议&#xff0c;如…

福州大学 2022~2023 学年第 1 学期考试 A 卷压轴题参考答案

题目&#xff1a; 定义一个抽象类Structure&#xff08;含有纯虚函数type函数&#xff0c;用以显示当前结构的类型&#xff1b; 含有show函数&#xff09;&#xff0c; 在此基础上派生出Building类, 用来存储一座楼房的层数、房间数以及它的总平方米数。 建立派生 类House&am…

QML 实现上浮后消失的提示框

基本效果&#xff1a;上浮逐渐显示&#xff0c;短暂停留后上浮逐渐消失 为了能同时显示多个提示框&#xff0c;一是需要动态创建每个弹框 Item&#xff0c;二是弹出位置问题&#xff0c;如果是底部为基准位置就把已经弹出的往上移动。 效果展示&#xff1a; 主要实现代码&…

路由模式--哈希模式下使用a标签跳转会有问题

路由模式分为 history 和 hash 两种模式&#xff0c;在 hash 模式下&#xff0c;使用 a 标签去跳转路由&#xff0c;可能会有问题。 比如&#xff1a; <a href"/home"><img src"/logo.png" class"logo" /></a> 在跳转路由时…

机器学习数学原理专题——线性分类模型:损失函数推导新视角——交叉熵

目录 二、从回归到线性分类模型&#xff1a;分类 3.分类模型损失函数推导——极大似然估计法 &#xff08;1&#xff09;二分类损失函数——极大似然估计 &#xff08;2&#xff09;多分类损失函数——极大似然估计 4.模型损失函数推导新视角——交叉熵 &#xff08;1&#x…

Java | Leetcode Java题解之第174题地下城游戏

题目&#xff1a; 题解&#xff1a; class Solution {public int calculateMinimumHP(int[][] dungeon) {int n dungeon.length, m dungeon[0].length;int[][] dp new int[n 1][m 1];for (int i 0; i < n; i) {Arrays.fill(dp[i], Integer.MAX_VALUE);}dp[n][m - 1] …

C语言入门系列:初识函数

文章目录 一&#xff0c;C语言函数与数学函数的区别1&#xff0c;回忆杀-初中数学2&#xff0c;C语言中的函数 二&#xff0c; 函数的声明1&#xff0c;函数头1.1&#xff0c;函数名称1.2&#xff0c;返回值类型1.3&#xff0c;参数列表 2&#xff0c;函数体2.1&#xff0c;函数…

idea右侧找不到Maven,在View-> Tool Windows下也找不到

正常情况Idea右侧没有Maven&#xff0c;只需去View -> Tool Windows 目录中找到Maven并点击Maven&#xff0c;Idea右侧就会出现 问题&#xff1a; idea右侧找不到Maven&#xff0c;在View -> Tool Windows 目录中也找不到Maven&#xff0c;下图 全局搜索ctrl N&#xff…

数据结构历年考研真题对应知识点(栈和队列的应用)

目录 3.3栈和队列的应用 3.3.2栈在表达式求值中的应用 【中缀表达式转后缀表达式的过程(2012、2014)】 【栈的深度分析(2009、2012)】 【用栈实现表达式求值的分析(2018)】 3.3.3栈在递归中的应用 【栈在函数调用中的作用和工作原理(2015、2017)】 3.3.5队列在计算机系…