概念:线段树(Segment Tree)是一种二叉搜索树,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。对于线段树中的每一个非叶子节点[a,b],它的左子树表示的区间为[a,(a+b)/2],右子树表示的区间为[(a+b)/2+1,b]。因此线段树是平衡二叉树。叶节点数目为N,即整个线段区间的长度。使用线段树可以快速的查找某一个节点在若干条线段中出现的次数,时间复杂度为O(logN)。——摘自维基百科
基本思路:把每个待处理的区间都拓展到完全二叉树最后一层的节点个数,形成完全二叉树,对于节点o,其左右子节点分别为o*2,o*2+1,叶子节点就是单个节点形成的区间,使用数组维护二叉树(简单高效)。
下面是在hiho oj网站涉及的所有线段树的题目及分析
点修改
Hiho18 :返回区间最小值
思路:使用数组minv[i]维护结点编号为i的结点区间的最小值.
- 查询操作:
结点的区间在查询区间内则直接返回结点对应最小值minv[o],否则分别对结点的左右子树查询相应最小值,然后取左右子树的较小值作为最小值。
- 修改操作:
主要是针对修改结点涉及的区间对应结点的最小值信息进行维护。
区间修改
Hiho20:区间修改(只设置),返回区间和
思路:使用数组setv[i]维护区间设置的值,setv[o]>=0表明在o结点区间内的值都是setv[o],setv[o]=-1表明o所在区间的值不尽相同或者此区间没有数值。
- 查询操作:
如果setv[o]>=0则直接n*setv[o]计算到sum中,否则进入o的子节点计算在查询区间的和。
- 修改操作:
如果结点区间在修改区间内,则直接setv[o]=v,否则对子节点的在修改区间的结点赋值,同时如果setv[o]>0则要将该值传递给子节点,同时该节点的
setv[o]变为-1(因为该区间数值出现了不一致)。
Hiho21:连续区间离散化
思路:在这道题目是通过线段树结点将连续型区间离散化,并且将连续区间归一化,减少空间复杂度(预处理详见题目链接提示部分),然后进行区间修改,之后的算法同hiho20,只是需要考虑连续区间进行分解时比较特殊:
离散型:区间[l,r]分解为[l,m] [m+1,r]
连续性:区间[l,r]分解为[l,m] [m,r]
hiho22:设置为某一值或增加一个增量(正负都可),返回总量
这里用了数组setv和sumv来维护区间值和区间和。所以查询操作直接返回sumv的第一个结点值就是总量,时间复杂度为O(1),对于两种修改操作首先明确:sumv[o] = sumv[2*o]+sumv[2*o+1].
- set型修改操作
和前面hiho20的基本一致,只是这里要同时维护sumv的信息。如果结点区间在修改区间,setv[o]=v并且sumv[o]做更新,否则在处理完子节点后使用
sumv[o] = sumv[2*o]+sumv[2*o+1]更新o结点
- add型修改操作
逻辑和set型基本一致,需要另外考虑的有两点,一是setv[o] += v时需要考虑是否超过题目要求的值的最小或最大值并作相应处理;二是add型操作在结点
的区间在修改区间内的情况下,如果setv[o]==-1,需要继续访问子节点知道找到setv[o]>=0时才能去更新setv[o]和sumv[o].
注:这四道题代码请参见我的github账号的leetcode仓库下。
总结
好多题目基本逻辑理解不难,但是要考虑周全并accept并不容易,主要是有些地方考虑不周。所以在提交代码前除了示例正确外可以认真考虑测试几个有代表性的测试用例,这样可以保证更高的通过率。