房子有17种颜色,随意在不同L–R范围内统一刷成颜色C,查询L–R范围内有多少种颜色?
提示:这是比较典型的线段树的应用
类似颜色这种题目,字节跳动改编来考过的,当时我不熟悉本题,所以也没做出来……
重要的线段树区间树的基础知识:
【1】线段树:区间树:逻辑上的二分法,以空间换时间完成一段区间上的add,update,query任务
【2】线段树区间树应用:一堆正方形下落,每次下落之后,组成的积木最高高度是多少
题目
房子有17种颜色,随意在不同L–R范围内统一刷成颜色C,查询L–R范围内有多少种颜色?
又是这种区间更新操作,和之前的基础题【2】非常类似,还有查询操作,这种涉及区间的操作就可以用线段树,整体标记
一、审题
示例:
不放设几种不同的颜色状态为
x y z f
房子最开始是这样的
x x x x
请你将1–2范围内刷成y
y y x x
请你将2–3刷成z
y z z x
请你将3–4刷成f
y z f f
查询1-4范围内有几种颜色?
显然3种
看本题之前,请你先搞清楚什么是线段树?
重要的线段树区间树的基础知识:
【1】线段树:区间树:逻辑上的二分法,以空间换时间完成一段区间上的add,update,query任务
//这个事情的本质就是:用额外空间,帮助加速查询累加和,达到优化时间复杂度的目的。
public static class SegmentTree{
//成员属性
//(1)sum数组:对应下标i处放信息:sum[i],它代表此时arr的l–r范围内的累加和【每次任务都可能让sum变化】
public int[] sum;
//(2)lazy数组:对应下标i处放信息:lazy[i],它代表此时arr的l–r范围内揽下了一个增加任务,
// 增加的值是多少呢?C,【揽下发音类似于lazy,所以取名为lazy,方便记忆】
public int[] lazy;
//(3)change数组:对应下标i处放信息:change[i],它代表此时arr的l–r范围内揽下了更新任务,
// 更新后arr的l–r统统为多少?全是C
public int[] change;
//(4)isUpdated数组:配合change数组用,当有更新任务时,isUpdated[i]=true,
// 代表l–r范围内就有更新任务,更新的具体任务看change数组,默认情况isUpdated[i]=false,
// 表示当前l–r范围内没有揽下更新任务
public boolean[] isChanged;
//转移arr0到arr
public int[] arr;//不要0下标
//构造函数
//构造的时候,就告知arr是啥,我好抹掉0下标
public SegmentTree(int[] arr0){
//先抹掉下标0
arr = new int[arr0.length + 1];
int N = arr.length;
for (int i = 0; i < arr0.length; i++) {
arr[i + 1] = arr0[i];
}
//现在开始都是操作arr,1--N
//初始化成员属性,4N宽度
sum = new int[N << 2];//x<<1=2x
lazy = new int[N << 2];//x<<1=2x
change = new int[N << 2];//x<<1=2x
isChanged = new boolean[N << 2];//x<<1=2x
}
//首次给你arr,请你用递归构建满二叉树,把累加和sum构建出来
//从arr的l--r上构建
//先建左,再建右,最后综合建我的
public void build(int l, int r, int i){
//从i=1开始填写,i=0这个位置的信息就代表l--r范围上的累加和sum
//base case,l==r,就是每一个元素触底
if (l == r) {
sum[i] = arr[l];//取lr均可
return;
}
//左右构建,并求和上来
int mid = l + ((r - l) >> 1);
build(l, mid, i << 1);//i<<1=2i
build(mid + 1, r, i << 1 | 1);//i<<1 | 1=2i+1
//然后把左右子,汇总给我sum[i]--不管啥任务,都是围绕变动sum而进行的
sumUp(i);//把i下标的左右子汇总给我i
}
//把i下标的左右子汇总给我i
public void sumUp(int i){
sum[i] = sum[i << 1] + sum[i << 1 | 1];
}
//成员方法
//检查l–r是否曾经接过旧任务,有就需要左右子分担
//从头结点,开始下发,然后咱们去找,哪些l–r段确实需要接下这个任务,下发任务之前,
// 检查之前我还有旧任务吗?有旧任务话,我需要左右子分担,然后我才能揽住新任务,
// 揽住了任务之后,干活(这个活,是更新任务哦),干完标记lazy或者change和isChanged,sum
public void checkPreTaskDown(int i, int ln, int rn){
//把i位置之前接的任务,分发下去给左子和右子分担,i上的任务时r-L+1个,那
//左子承担mid - l + 1个
//右子承担r-mid个
//不过先查update任务,它可以干废add任务
//更新任务涉及sum,change,isChanged,lazy
if (isChanged[i]){//之前i的l--r范围内有任务,下发
lazy[i << 1] = 0;//左右子的add任务,被update废了
lazy[i << 1 | 1] = 0;//左右子的add任务,被update废了
sum[i << 1] = ln * change[i];//change中放着i更新为谁了,你现在就可以分发
sum[i << 1 | 1] = rn * change[i];//change中放着i更新为谁了,你现在就可以分发
change[i << 1] = change[i];//左子右子的change完全承接i
change[i << 1 | 1] = change[i];//左子右子的change完全承接i
isChanged[i << 1] = true;//左子右子的isChanged完全承接i
isChanged[i << 1 | 1] = true;//左子右子的isChanged完全承接i
//然后更新任务重要的一步就是废掉add,同时自己的update也清空
isChanged[i] = false;//我i的update任务没了
lazy[i] = 0;
}
//后检查add任务
//add任务涉及sum,lazy
if (lazy[i] != 0){//确实接过任务,干
sum[i << 1] += ln * lazy[i];//加过的C给左右子
sum[i << 1 | 1] += ln * lazy[i];//加过的C给左右子
lazy[i << 1] += lazy[i];//lazy记录纸i的l--r要加的C,给左右子
lazy[i << 1 | 1] += lazy[i];//lazy记录纸i的l--r要加的C,给左右子
lazy[i] = 0;//然后清掉我i的任务
}
}
//add(L,R,C,l,r,i)任务
public void add(int L, int R, int C, int l, int r, int i){
//带着L--R上要加C的任务,来到i位置,i位置代表l--r范围内的信息,决定分下去还是不分?
//(1)如果L–R恰好包住l–r,即L<=l && r<=R,说明l–r上直接可以揽住这个任务,
// 不要再往下发了,咱直接在l–r上做加C的任务【然后汇总信息往父节点报,调用 sumUp(i);】
if (L <= l && r <= R){
//干
sum[i] += (r - l + 1) * C;
lazy[i] += C;//增加任务,叠加的关系
return;//base case结束,返回,看看左右子还需要下发吗,不需要就汇总信息返回
}
//(0)每次干任务之前,检查一下之前有没有来过任务,之前有任务的话,我接不住,
// 要把这个旧任务给左右子干,然后我才能接当前这个新任务,
// 调用sendTaskDown(int i, int ln, int rn),i的左子,右子,分别干ln和rn个,替我分忧。
int mid = l + ((r - l) >> 1);
checkPreTaskDown(i, mid - l + 1, r - mid);
//(2)mid=(l+r)/2,如果L<mid,说明L–R的任务还需要让左子树知道,
// 故需要下发给左子树l–mid范围,即调用add(L,R,C,l,mid,i)
if (L <= mid) add(L, R, C, l, mid, i << 1);
//(3)mid=(l+r)/2,如果R>mid,说明L–R的任务还需要让右子树知道,故需要下发给右子树mid+1–r范围,
// 即调用add(L,R,C,mid+1,r,i)
if (R > mid) add(L, R, C, mid + 1, r, i << 1 | 1);
//下发完最新任务给左右子,汇总左右子信息返回给我sum
sumUp(i);
}
//update(L,R,C,l,r,i)任务
public void update(int L, int R, int C, int l, int r, int i) {
//带着L--R上要变C的任务,来到i位置,i位置代表l--r范围内的信息,决定分下去还是不分?
//(1)如果L–R恰好包住l–r,即L<=l && r<=R,说明l–r上直接可以揽住这个任务,不要再往下发了,
// 咱直接在l–r上做 变 C的任务【然后汇总信息往父节点报,调用 sumUp(i);】
if (L <= l && r <= R){
//干,整体更新,覆盖增加任务
sum[i] = (r - l + 1) * C;
lazy[i] = 0;//更新任务直接会废掉增加任务的
change[i] = C;//增加任务,叠加的关系
isChanged[i] = true;//标记i的l--r范围内有更新任务
return;//base case结束,返回,看看左右子还需要下发吗,不需要就汇总信息返回
}
//(0)每次干任务之前,检查一下之前有没有来过任务,之前有任务的话,我接不住,
// 要把这个旧任务给左右子干,然后我才能接当前这个新任务,
// 调用sendTaskDown(int i, int ln, int rn),i的左子,右子,分别干ln和rn个,替我分忧。
int mid = l + ((r - l) >> 1);
checkPreTaskDown(i, mid - l +1, r - mid);
//(2)mid=(l+r)/2,如果L<mid,说明L–R的任务还需要让左子树知道,
// 故需要下发给左子树l–mid范围,即调用update(L,R,C,l,mid,i)
if (L <= mid) update(L, R, C, l, mid, i << 1);
//(3)mid=(l+r)/2,如果R>mid,说明L–R的任务还需要让右子树知道,
// 故需要下发给右子树mid+1–r范围,即调用update(L,R,C,mid+1,r,i)
if (R > mid) update(L, R, C, mid + 1, r, i << 1 | 1);
//下发完最新任务给左右子,汇总左右子信息返回给我sum
sumUp(i);
}
//query(L,R,l,r,i)任务
public int query(int L, int R, int l, int r, int i) {
//带着L--R上要查找L--R上累加和的任务,来到i位置,i位置代表l--r范围内的信息,决定分下去还是不分?
//(1)如果L–R恰好包住l–r,即L<=l && r<=R,说明l–r上直接可以揽住这个任务,不要再往下发了,
// 直接返回sum[i]
if (L <= l && r <= R) {
//干,整体找到了这个范围,直接返回sum[i],代表覆盖l--r的arr的累加和
return sum[i];
}
//(0)每次干任务之前,检查一下之前有没有来过任务,之前有任务的话,我接不住,
// 要把这个旧任务给左右子干,然后我才能接当前这个新任务,
// 调用sendTaskDown(int i, int ln, int rn),i的左子,右子,分别干ln和rn个,替我分忧。
int mid = l + ((r - l) >> 1);
checkPreTaskDown(i, mid - l +1, r - mid);
//准备ans=0,去收集左右的信息,融合加起来给ans,最后作为返回结果
int ans = 0;
//(2)mid=(l+r)/2,如果L<mid,说明L–R的任务还需要让左子树知道,
// 故需要下发给左子树l–mid范围,即调用query(L,R,l,mid,i)
if (L <= mid) ans += query(L, R, l, mid, i << 1);
//(3)mid=(l+r)/2,如果R>mid,说明L–R的任务还需要让右子树知道,
// 故需要下发给右子树mid+1–r范围,即调用query(L,R,mid+1,r,i)
if (R > mid) ans += query(L, R, mid + 1, r, i << 1 | 1);
//(4)ans=(2)+(3),返回ans;
return ans;
}
}
17种颜色的表示
咱们示例用了xyz,但是咱们统计的时候,是怎么统计的?
线段树是需要去l–r范围内查找L–R的颜色种类的?
我们现在只知道颜色是xyz,你咋统计呢?
(1)每个范围内l–r内的线段树下标时i,要不我们用一个哈希集来存这些颜色xyz,统计的时候,用set.size()就能表示颜色的数量
当然,哈希集就会耗费很多空间,也不是不行
(2)还有个办法,左神讲得,咱们int数字,通过0 1编码的不同方式来表示不同颜色的状态
比如
没有颜色就是000–00,a0=0,正常int类型的数字a0就是32位二进制数(计算机中存的就是这个数)
如果是1号颜色:000—010,在低位1那个地方写1,数字a1=2,相当于是1向左移动1位
如果是2号颜色:000—100,继续在低位2那个地方写1,数字a2=4,相当于是1向左移动2位
如果是3号颜色:000–1000,继续在低位3那个地方写1,数字a2=8,相当于是1向左移动3位
……
如果是17号颜色:1000000000000000,继续在低位17那个地方写1,数字a=相当于是1向左移动17位
有了这些数字,你看看如果你刷颜色C,会是啥感觉?你咋刷,怎么统计颜色种类?
刷颜色: 实际上直接让新颜色替代它就行,然后这个范围内颜色的状态就变了
比如把a2刷为a1,让a1替代就行
统计颜色:实际上是范围内所有颜色号的或关系,统计目前这个或结果的1的个数
比如,1–2范围内的房子颜色为:1号颜色,2号颜色
ans = a1&a2就是最终的状态,里面有多少种颜色呢?
统计ans 里头的1的个数,不就是2吗
是不是很妙?通过这种状态表示各种颜色的情况,或运算就能搞定颜色的数量
咱们看示例:
不放设几种不同的颜色状态为
a1 a2 a3 a4
房子最开始是这样的
a1 a1 a1 a1
请你将1–2范围内刷成a2
a2 a2 x x
请你将2–3刷成a3
a2 a3 a3 x
请你将3–4刷成a4
a2 a3 a4 a4
查询1-4范围内有几种颜色?
a2|a3|a4|a4
显然3种
是不是第二种方法超级简单
统计一个状态中的1的个数,32位检查一下,相当于o(32)=o(1)速度
咱们之前学过位运算操作,里面统计1的个数,需要用到一个很重要的知识点:
【3】如何把一个数字x最右侧那个1拿出来,变成00…10…的格式
统计数字x最右侧那个1的操作
rightOne=x取反加1再与x
获取数字x最右侧1的办法:让x取反加1再与x
获取数字x最右侧1的办法:让x取反加1再与x
获取数字x最右侧1的办法:让x取反加1再与x
熟练给我记住啊!
有了这个基础知识
然后咱们统计任意数字x的1的个数:
(1)每次将x不为0,计数器count++
(2)将x右侧1取出来,rightOne取反,拿去与x,抹掉最右侧那个1
(3)继续去(1),也就统计出来了
还有一个办法统计数字1的个数,更简单:
(1)每次x的0位置数字不为0,计数器count++
(2)x向右移动1位,继续去(1)
貌似这个办法更好呢?
//统计一个状态中的1的个数,32位检查一下,相当于o(32)=o(1)速度
public static int numOfOne(int x){
int count = 0;
int tmp = x;
for(; tmp != 0; tmp >>= 1){
//(1)每次x不为0,计数器count++
if ((tmp & 1) != 0) count++;
//(2)x向右移动1位,继续去(1)
}
return count;
}
public static void test(){
int x = 14;
System.out.println(numOfOne(x));
}
public static void main(String[] args) {
test();
}
没错就是这个办法统计1的个数,o(32)=o(1),问题不大的
3
线段树解决本题
有了颜色号的表示,咱们来破解本题
咱们建立一个线段树,SegmentTreeColor
成员变量有:
(1)sum数组,表示arr房子的l–r范围内的颜色状态,说白了就是l–r各种颜色色号的或结果。不是颜色种类哦!!!
(2)change数组,表示更新任务,在L–R范围上,将所有颜色变为啥颜色C?记录下
(3)isChanged数组,配合change用,true,表示确实有承接这个任务
成员方法有:
(4)一开始,就把所有范围内的颜色状态到sum数组中,后续更新再修改
这好说,去左边收集颜色组合的状态,右边收集颜色组合的状态,然后sumUp,汇总给我i位置,这个状态,里头的1个数,就表示l–r内整体颜色种类有这么多
(5)checkPreTaskDown(int i, int ln, int rn)方法,下发任务之前,先检查之前l–r范围内是否有过更新任务,有就先下发给左右子干,清空我i手里的任务,才能继续拦新的更新任务
(6)更新任务update(int L, int R, int C, int l, int r, int i),接到L–R将其变为颜色C的任务,从头结点1号下标i开始下发,代表去1–N范围内搞颜色去,
(7)查询任务query(int L, int R, int l, int r, int i),接到查询L–R将内总共有多少颜色的任务,从头结点1号下标i开始下发,代表去1–N范围内查询去
okay,有了这些思想,你熟悉了线段树基础知识,那就可以改写本题的代码了,非常妙!!!
public static class SegmentTreeColor{
//成员属性
//(1)sum数组:对应下标i处放信息:sum[i],sum数组,
// sum数组,表示arr房子的l--r范围内的**颜色状态**,说白了就是l--r各种颜色色号的或结果。不是颜色种类哦!!!
// 【每次任务都可能让sum变化】
public int[] sum;
//(3)change数组:对应下标i处放信息:change[i],它代表此时arr的l–r范围内揽下了更新任务,
// 更新后arr的l–r统统为多少?全是C
public int[] change;
//(4)isUpdated数组:配合change数组用,当有更新任务时,isUpdated[i]=true,
// 代表l–r范围内就有更新任务,更新的具体任务看change数组,默认情况isUpdated[i]=false,
// 表示当前l–r范围内没有揽下更新任务
public boolean[] isChanged;
//转移arr0到arr
public int[] arr;//不要0下标
//构造函数
//构造的时候,就告知arr是啥,我好抹掉0下标
public SegmentTreeColor(int[] arr0){
//先抹掉下标0
arr = new int[arr0.length + 1];
int N = arr.length;
for (int i = 0; i < arr0.length; i++) {
arr[i + 1] = arr0[i];
}
//现在开始都是操作arr,1--N
//初始化成员属性,4N宽度
sum = new int[N << 2];//x<<1=2x
change = new int[N << 2];//x<<1=2x
isChanged = new boolean[N << 2];//x<<1=2x
}
//首次给你arr,请你用递归构建满二叉树,把颜色种数sum构建出来
//从arr的l--r上构建
//先建左,再建右,最后综合建我的
public void build(int l, int r, int i){
//从i=1开始填写,i=0这个位置的信息就代表l--r范围上的累加和sum
//base case,l==r,就是每一个元素触底
if (l == r) {
sum[i] = arr[l];//取lr均可,它这个状态1的个数是多少?
return;
}
//左右构建,并求和上来
int mid = l + ((r - l) >> 1);
build(l, mid, i << 1);//i<<1=2i
build(mid + 1, r, i << 1 | 1);//i<<1 | 1=2i+1
//然后把左右子,汇总给我sum[i]--不管啥任务,都是围绕变动sum而进行的
sumUp(i);//把i下标的左右子汇总给我i
}
//把i下标的左右子汇总给我i
//sum[i]代表覆盖l--r的arr的颜色状态的或运算总和
public void sumUp(int i){
sum[i] = sum[i << 1] | sum[i << 1 | 1];//汇总时,咱们或这个结果
}
//检查l–r是否曾经接过旧任务,有就需要左右子分担
//从头结点,开始下发,然后咱们去找,哪些l–r段确实需要接下这个任务,下发任务之前,
// 检查之前我还有旧任务吗?有旧任务话,我需要左右子分担,然后我才能揽住新任务,
// 揽住了任务之后,干活(这个活,是更新任务哦),干完标记lazy或者change和isChanged,sum
public void checkPreTaskDown(int i, int ln, int rn){
//把i位置之前接的任务,分发下去给左子和右子分担,i上的任务时r-L+1个,那
//左子承担mid - l + 1个
//右子承担r-mid个
//不过先查update任务,它可以干废add任务
//更新任务涉及sum,change,isChanged,lazy
if (isChanged[i]){//之前i的l--r范围内有任务,下发
sum[i << 1] = change[i];//change中放着i更新为谁了,你现在就可以分发
sum[i << 1 | 1] = change[i];//change中放着i更新为谁了,你现在就可以分发
change[i << 1] = change[i];//左子右子的change完全承接i
change[i << 1 | 1] = change[i];//左子右子的change完全承接i
isChanged[i << 1] = true;//左子右子的isChanged完全承接i
isChanged[i << 1 | 1] = true;//左子右子的isChanged完全承接i
//然后更新任务重要的一步就是废掉add,同时自己的update也清空
isChanged[i] = false;//我i的update任务没了
}
}
//update(L,R,C,l,r,i)任务
public void update(int L, int R, int C, int l, int r, int i) {
//带着L--R上要变C的任务,来到i位置,i位置代表l--r范围内的信息,决定分下去还是不分?
//(1)如果L–R恰好包住l–r,即L<=l && r<=R,说明l–r上直接可以揽住这个任务,不要再往下发了,
// 咱直接在l–r上做 变 C的任务【然后汇总信息往父节点报,调用 sumUp(i);】
if (L <= l && r <= R){
//干,整体更新,覆盖增加任务
sum[i] = C;//你整个范围内都变C,不管或多少次,都是一种色号
change[i] = C;
isChanged[i] = true;//标记i的l--r范围内有更新任务
return;//base case结束,返回,看看左右子还需要下发吗,不需要就汇总信息返回
}
//(0)每次干任务之前,检查一下之前有没有来过任务,之前有任务的话,我接不住,
// 要把这个旧任务给左右子干,然后我才能接当前这个新任务,
// 调用sendTaskDown(int i, int ln, int rn),i的左子,右子,分别干ln和rn个,替我分忧。
int mid = l + ((r - l) >> 1);
checkPreTaskDown(i, mid - l +1, r - mid);
//(2)mid=(l+r)/2,如果L<mid,说明L–R的任务还需要让左子树知道,
// 故需要下发给左子树l–mid范围,即调用update(L,R,C,l,mid,i)
if (L <= mid) update(L, R, C, l, mid, i << 1);
//(3)mid=(l+r)/2,如果R>mid,说明L–R的任务还需要让右子树知道,
// 故需要下发给右子树mid+1–r范围,即调用update(L,R,C,mid+1,r,i)
if (R > mid) update(L, R, C, mid + 1, r, i << 1 | 1);
//下发完最新任务给左右子,汇总左右子信息返回给我sum
sumUp(i);
}
//query(L,R,l,r,i)任务
public int query(int L, int R, int l, int r, int i) {
//带着L--R上要查找L--R上累加和的任务,来到i位置,i位置代表l--r范围内的信息,决定分下去还是不分?
//(1)如果L–R恰好包住l–r,即L<=l && r<=R,说明l–r上直接可以揽住这个任务,不要再往下发了,
// 直接返回sum[i]
if (L <= l && r <= R) {
//干,整体找到了这个范围,直接返回sum[i],代表覆盖l--r的arr的颜色状态的或运算总和
return numOfOne(sum[i]);//返回颜色种类哦
}
//(0)每次干任务之前,检查一下之前有没有来过任务,之前有任务的话,我接不住,
// 要把这个旧任务给左右子干,然后我才能接当前这个新任务,
// 调用sendTaskDown(int i, int ln, int rn),i的左子,右子,分别干ln和rn个,替我分忧。
int mid = l + ((r - l) >> 1);
checkPreTaskDown(i, mid - l +1, r - mid);
//准备ans状态的1的个数,去收集左右的信息,融合加起来给ans,最后作为返回结果
int ans = 0;
//(2)mid=(l+r)/2,如果L<mid,说明L–R的任务还需要让左子树知道,
// 故需要下发给左子树l–mid范围,即调用query(L,R,l,mid,i)
if (L <= mid) ans += query(L, R, l, mid, i << 1);
//(3)mid=(l+r)/2,如果R>mid,说明L–R的任务还需要让右子树知道,
// 故需要下发给右子树mid+1–r范围,即调用query(L,R,mid+1,r,i)
if (R > mid) ans += query(L, R, mid + 1, r, i << 1 | 1);
//(4)ans=(2)|(3),返回ans中1的个数;
return ans;
}
//统计1的个数
public int numOfOne(int x){
int count = 0;
int tmp = x;
for(; tmp != 0; tmp >>= 1){
//(1)每次x不为0,计数器count++
if ((tmp & 1) != 0) count++;
//(2)x向右移动1位,继续去(1)
}
return count;
}
}
里面的这段代码很重要啊,范围内整体颜色状态的总和是或关系,这样我们才能统计1的个数
还有query时,如果范围刚刚包上,咱们返回的是sum[i]的1的个数,这才是咱们最终要的结果。其余的ans都是叠加个数关系。
这个题的代码,当时左神没给哦,
他当时只提了一嘴如何表示颜色的状态,我今天想了2个小时,写出来了。
我只能给自己说:牛逼!!!!
佩服我自己!!!
毫不夸张地说,这个知识点,我已经全然掌握!!!
手撕线段树解决本题的流程代码
有了线段树SegmentTreeColor
(1)咱们就可以将arr房子灌进去,先统计sum
(2)然后接更新任务,查询任务,测试
流程代码就很简单了
public static void test2(){
//造颜色代号
int[] color = new int[18];//01--17颜色编号
for (int i = 1; i < 18; i++) {
color[i] = 1 << i;//向左移动i位,然后那就是1,代表颜色色号
}
//房子一堆,就用案例中的颜色论证一下
//不放设几种不同的颜色状态为
//房子最开始是这样的
//a1 a1 a1 a1
int[] arr = {color[1], color[1], color[1], color[1]};//2 2 2 2
SegmentTreeColor segmentTreeColor = new SegmentTreeColor(arr);
int N = segmentTreeColor.arr.length - 1;
int[] sum = segmentTreeColor.sum;
System.out.println("arr的sum数组");
segmentTreeColor.build(1, N, 1);//从i=1开始构建arr的1--N范围
for(Integer i : sum) System.out.print(i +" ");
System.out.println();
//请你将1--2范围内刷成a2
//a2 a2 x x
segmentTreeColor.update(1, 2, color[2], 1, N, 1);
sum = segmentTreeColor.sum;
System.out.println("更新arr的sum数组");
for(Integer i : sum) System.out.print(i +" ");
System.out.println();
//请你将2--3刷成a3
//a2 a3 a3 x
segmentTreeColor.update(2, 3, color[3], 1, N, 1);
sum = segmentTreeColor.sum;
System.out.println("更新arr的sum数组");
for(Integer i : sum) System.out.print(i +" ");
System.out.println();
//请你将3--4刷成a4
//a2 a3 a4 a4
segmentTreeColor.update(3, 4, color[4], 1, N, 1);
sum = segmentTreeColor.sum;
System.out.println("更新arr的sum数组");
for(Integer i : sum) System.out.print(i +" ");
System.out.println();
//查询1-4范围内有几种颜色?
//a2|a3|a4|a4
int ans = segmentTreeColor.query(1, 4, 1, N, 1);
System.out.println(ans);
ans = segmentTreeColor.query(1, 2, 1, N, 1);
System.out.println(ans);
}
public static void main(String[] args) {
// test();
test2();
}
测试结果:
arr的sum数组
0 2 2 2 2 2 2 2 0 0 0 0 0 0 0 0 0 0 0 0
更新arr的sum数组
0 6 4 2 2 2 2 2 0 0 0 0 0 0 0 0 0 0 0 0
更新arr的sum数组
0 14 12 10 4 8 8 2 0 0 0 0 0 0 0 0 0 0 0 0
更新arr的sum数组
0 28 12 16 4 8 8 2 0 0 0 0 0 0 0 0 0 0 0 0
3
2
是不是屌爆了!!!
总结
提示:重要经验:
1)线段树,区间树,利用额外空间换时间,二分统计重要信息,然后每次给定特定区间的任务,下发下去,搞完汇总信息返回
2)基础线段树的知识,非常重要,理解之后,咱们这个题,只需要轻而易举就能改出代码来,本题其实还有颜色的表示方法,非常重要,利用状态不同表示,这样,不同颜色或起来,1的个数就是颜色种树。
3)笔试求AC,可以不考虑空间复杂度,但是面试既要考虑时间复杂度最优,也要考虑空间复杂度最优。