对于在线数据且数据量大,无法离散化,则无法实现构造线段树。那么此时可以动态开点线段树,即在modify和qurey时来创建节点。事先只需要构建一个根节点即可。此外一个节点的左右子节点无法根据 u ≪ 1 u\ll1 u≪1以及 u ≪ 1 ∣ 1 u\ll1|1 u≪1∣1,所以在定义节点时需要定义节点的左右子节点。其余的pushup,pushdown,modify以及query都与传统线段树类似。
例题1 leetcode732. 我的日程安排表 III
当 k 个日程安排有一些时间上的交叉时(例如 k 个日程安排都在同一时间内),就会产生 k 次预订。
给你一些日程安排 [start, end) ,请你在每个日程安排添加后,返回一个整数 k ,表示所有先前日程安排会产生的最大 k 次预订。
实现一个 MyCalendarThree 类来存放你的日程安排,你可以一直添加新的日程安排。
MyCalendarThree() 初始化对象。
int book(int start, int end) 返回一个整数 k ,表示日历中存在的 k 次预订的最大值。
思路
线段树节点信息包括:左右端点、左右子节点、区间最大值,懒标记
每次book时,将对应子区间的最大值+1,查询的是所有区间的最大值,也就是根节点的区间最大值属性。
代码
class MyCalendarThree {
class Node {
Node l,r;
int ll, rr;
int v,c;
public Node(int ll, int rr) {
this.ll = ll;
this.rr = rr;
}
public Node getLeft() {
if(this.l == null) {
this.l = new Node(ll, ll + (rr - ll) / 2);
}
return this.l;
}
public Node getRight() {
if(this.r == null) {
this.r = new Node(ll + (rr - ll) / 2 + 1, rr);
}
return this.r;
}
}
Node root;
public MyCalendarThree() {
root = new Node(0,(int)(1e9));
}
public int book(int start, int end) {
modify(root, start, end - 1, 1);
return root.v;
}
public void modify(Node root, int l, int r, int c) {
if(root.ll > r || root.rr < l) {
return;
}
if(root.ll >= l && root.rr <= r) {
root.v += c;
root.c += c;
} else {
pushdown(root);
modify(root.getLeft(), l, r, c);
modify(root.getRight(), l, r, c);
pushup(root);
}
}
public void pushup(Node root) {
root.v = Math.max(root.getLeft().v, root.getRight().v);
}
public void pushdown(Node root) {
if(root.c > 0) {
root.getLeft().v += root.c;
root.getLeft().c += root.c;
root.getRight().v += root.c;
root.getRight().c += root.c;
root.c = 0;
}
}
}
/**
* Your MyCalendarThree object will be instantiated and called as such:
* MyCalendarThree obj = new MyCalendarThree();
* int param_1 = obj.book(start,end);
*/
例题2 leetcode731. 我的日程安排表 II
实现一个 MyCalendar 类来存放你的日程安排。如果要添加的时间内不会导致三重预订时,则可以存储这个新的日程安排。
MyCalendar 有一个 book(int start, int end)方法。它意味着在 start 到 end 时间内增加一个日程安排,注意,这里的时间是半开区间,即 [start, end), 实数 x 的范围为, start <= x < end。
当三个日程安排有一些时间上的交叉时(例如三个日程安排都在同一时间内),就会产生三重预订。
每次调用 MyCalendar.book方法时,如果可以将日程安排成功添加到日历中而不会导致三重预订,返回 true。否则,返回 false 并且不要将该日程安排添加到日历中。
请按照以下步骤调用MyCalendar 类: MyCalendar cal = new MyCalendar(); MyCalendar.book(start, end)
思路
同上,只是添加日历前需要判断当前区间的最大值是否 < 2 <2 <2,只有满足才能插入。
代码
class MyCalendarTwo {
class Node {
int ll,rr;
Node l,r;
int v,c;
public Node(int l, int r) {
this.ll = l;
this.rr = r;
}
public Node getLeft() {
if(this.l == null) {
this.l = new Node(ll, ll + (rr - ll) / 2);
}
return this.l;
}
public Node getRight() {
if(this.r == null) {
this.r = new Node(ll + (rr - ll) / 2 + 1, rr);
}
return this.r;
}
}
Node root;
public MyCalendarTwo() {
root = new Node(0, (int)(1e9));
}
public boolean book(int start, int end) {
if(query(root, start, end - 1) >= 2) {
return false;
}
modify(root,start, end - 1, 1);
return true;
}
public int query(Node root, int l, int r) {
if(root.ll > r || root.rr < l) {
return 0;
}
if(root.ll >= l && root.rr <= r) {
return root.v;
} else {
pushdown(root);
return Math.max(query(root.getLeft(), l, r), query(root.getRight(), l, r));
}
}
public void pushup(Node root) {
root.v = Math.max(root.getRight().v, root.getLeft().v);
}
public void pushdown(Node root) {
if(root.c != 0) {
root.getLeft().v += root.c;
root.getLeft().c += root.c;
root.getRight().v += root.c;
root.getRight().c += root.c;
root.c = 0;
}
}
public void modify(Node root, int l, int r, int c) {
if(root.ll > r || root.rr < l) {
return;
}
if(root.ll >= l && root.rr <= r) {
root.v += c;
root.c += c;
} else {
pushdown(root);
modify(root.getLeft(), l, r, c);
modify(root.getRight(), l , r, c);
pushup(root);
}
}
}
/**
* Your MyCalendarTwo object will be instantiated and called as such:
* MyCalendarTwo obj = new MyCalendarTwo();
* boolean param_1 = obj.book(start,end);
*/
例题3 leetcode715. Range 模块
Range模块是跟踪数字范围的模块。设计一个数据结构来跟踪表示为 半开区间 的范围并查询它们。
半开区间 [left, right) 表示所有 left <= x < right 的实数 x 。
RangeModule 类:
RangeModule() 初始化数据结构的对象。
void addRange(int left, int right) 添加 半开区间 [left, right),跟踪该区间中的每个实数。添加与当前跟踪的数字部分重叠的区间时,应当添加在区间 [left, right) 中尚未跟踪的任何数字到该区间中。
boolean queryRange(int left, int right) 只有在当前正在跟踪区间 [left, right) 中的每一个实数时,才返回 true ,否则返回 false 。
void removeRange(int left, int right) 停止跟踪 半开区间 [left, right) 中当前正在跟踪的每个实数。
思路
线段树节点有属性(总和)表示当前区间所追踪的数的个数(因为left,right都是整数,所以不用考虑实数)。此外有懒标记表示当前区间是否被添加(1)或者移除(0),这样设置有利于计算区间总和,默认是-1。需要注意pushdown之后需要设置父节点的懒标记为默认值-1
class RangeModule {
class Node {
int ll, rr;
Node l,r;
int v;
int c;
public Node(int l, int r) {
this.ll = l;
this.rr = r;
c = -1;
}
public Node getLeft() {
if(this.l == null) {
this.l = new Node(ll,ll + (rr - ll) / 2);
}
return this.l;
}
public Node getRight() {
if(this.r == null) {
this.r = new Node(ll + (rr - ll) / 2 + 1, rr);
}
return this.r;
}
}
Node root;
public RangeModule() {
root = new Node(1,(int)(1e9));
}
public void addRange(int left, int right) {
modify(root,left,right-1,1);
}
public boolean queryRange(int left, int right) {
return query(root,left,right-1) == right - left;
}
public void removeRange(int left, int right) {
modify(root,left,right-1,0);
}
public int query(Node root, int l, int r) {
if(root.ll > r || root.rr < l) {
return 0;
}
if(root.ll >= l && root.rr <= r) {
return root.v;
} else {
pushdown(root);
int res = 0;
res += query(root.getLeft(),l,r);
res += query(root.getRight(),l,r);
return res;
}
}
public void pushup(Node root) {
root.v = root.getLeft().v + root.getRight().v;
}
public void pushdown(Node root) {
if(root.c != -1) {
root.getLeft().v = (root.getLeft().rr - root.getLeft().ll + 1) * root.c; root.getLeft().c = root.c;
root.getRight().v = (root.getRight().rr - root.getRight().ll + 1) * root.c; root.getRight().c = root.c;
root.c = -1;
}
}
public void modify(Node root, int l, int r, int d) {
if(root.ll > r || root.rr < l) {
return;
}
if(root.ll >= l && root.rr <= r) {
root.v = (root.rr - root.ll + 1) * d;
root.c = d;
} else {
pushdown(root);
modify(root.getLeft(),l,r,d);
modify(root.getRight(),l,r,d);
pushup(root);
}
}
}
/**
* Your RangeModule object will be instantiated and called as such:
* RangeModule obj = new RangeModule();
* obj.addRange(left,right);
* boolean param_2 = obj.queryRange(left,right);
* obj.removeRange(left,right);
*/
此外也可以使用平衡树TreeMap来求解
class RangeModule {
TreeMap<Integer, Integer> map;
public RangeModule() {
map = new TreeMap<>();
}
public void addRange(int left, int right) {
if(map.floorKey(right) == null) {
map.put(left, right);
} else if(map.floorKey(left) == null || map.get(map.floorKey(left)) < left) {
while(true) {
if(map.ceilingKey(left) == null || map.ceilingKey(left) >= right) {
if(map.ceilingKey(left) != null && map.ceilingKey(left) == right) {
map.put(left, map.get(right));
} else {
map.put(left,right);
}
break;
}
int k = map.ceilingKey(left);
int v = map.get(k);
map.remove(k);
if(v >= right) {
map.put(left, v);
break;
}
}
} else {
int k = map.floorKey(left);
int v = map.get(k);
map.remove(k);
while(true) {
if(map.ceilingKey(left) == null || map.ceilingKey(left) >= right) {
if(map.ceilingKey(left) != null && map.ceilingKey(left) == right) {
map.put(k,map.get(right));
} else {
map.put(k,Math.max(v,right));
}
break;
}
int k1 = map.ceilingKey(left);
int v1 = map.get(k1);
map.remove(k1);
if(v1 >= right) {
map.put(k, v1);
break;
}
}
}
}
public boolean queryRange(int left, int right) {
if(map.floorKey(left) == null || map.get(map.floorKey(left)) < right) {
return false;
}
return true;
}
public void removeRange(int left, int right) {
if(map.floorKey(left) != null) {
int k = map.floorKey(left);
int v = map.get(k);
if(v > left) {
map.remove(k);
if(v <= right) {
map.put(k,left);
} else {
map.put(k,left);
map.put(right,v);
}
}
}
while(true) {
if(map.ceilingKey(left) == null || map.ceilingKey(left) >= right) {
break;
}
int k = map.ceilingKey(left);
int v = map.get(k);
map.remove(k);
if(v > right) {
map.put(right, v);
break;
}
}
}
}
/**
* Your RangeModule object will be instantiated and called as such:
* RangeModule obj = new RangeModule();
* obj.addRange(left,right);
* boolean param_2 = obj.queryRange(left,right);
* obj.removeRange(left,right);
*/