好久没有刷题了,这两天在复习线段树,顺便就记录一下本题的解题过程。
点击这里看题

所以其实我们只需关注每一个点在它之前出现的那些点是否在这个点与原点所围成的矩形中就行,在这个矩形中有几个点那么该点的level就是几。
比如说,我输入[[1,1], [5,1], [7,1], [3,3], [5,5]],先看第一个点,[1,1]之前还没有点出现,所以很明显[1,1]的level就是0,level[0] ++,对于第二个点,[5,1]与原点所围成的矩形中出现了[1,1],所以[5,1]的level就是1,level[1] ++,第三个点[7,1]与原点围成的矩形中有[1,1]和[5,1],那么[7,1]的level就是2,level[2] ++;对于[3,3],它与原点所围成的矩形中是[1,1], 所以[3,3]的level是1,level[1] ++;最后是[5,5],它与原点围成的矩形中有[1,1],[5,1]和[3,3],所以[5,5]的level就是3,level[3] ++; 所以最后得出来的level数组为[1,2,1,1,0].
到这里,相信你已经看懂了题目,那么该怎么解呢?
暴力法
暴力大家应该都想得出来,两重for循环,既然y坐标是按照升序的,那么我只要考虑在当前点之前的点的x坐标是否不大于当前点的x坐标即可,第一重for里面代表是当前点,第二重for是当前点之前的点。
思考一下
如果用暴力的话,对于数据量比较小的时候还能hold得住,但是数据量一大呢,两重for的时间复杂度可是O(n^2),如果我输入的是一万个数呢,那么就要跑10000 * 10000次啊,要是再多点呢?显然这是不行的。那么我们再思考一下,对于某个点(tx, ty),它的y坐标我们不用考虑了,对于x坐标,我们只要找出在它之前出现的点中,哪些点的x坐标不就行了吗,统计出来假设有n个点,那么level[n]++ 即可。所以问题到这边就转换成了
的问题了。熟悉线段树的同学应该知道线段树就是用来高效解决这类求区间和的问题的,还不了解线段树的点击这里
线段树解法
我们用线段树去维护star的x区间,每次读入一个点,就去更新线段树(这里我把建树和更新放在一起操作了),更新完之后就去读取区间和,代码如下:
var maxn = 0; //x坐标的边界
var level = [];
var tree = []; //维护x坐标区间
function getStarLevel(stars) {
maxn = 0;
level = [];
for(let i = 0; i < stars.length; i ++)
level[i] = 0;
tree = [];
for(let i = 0; i < stars.length; i ++) {
if(stars[i][0] > maxn)
maxn = stars[i][0];
}
for(let i = 0; i < 4 * maxn; i ++) //线段树的缺点,空间消耗大
tree[i] = 0; //初始化线段树
for(let i = 0; i < stars.length; i ++) {
build_tree(1, 0, maxn, stars[i][0]); //在线建树
level[find(1, 0, maxn, 0, stars[i][0]) - 1] ++; //这边 -1 是不能把自身给算上去
}
return level;
}
/*
* 递归建树
* */
function build_tree(id, left, right, x) {
if(left === right) {
tree[id] ++;
return;
}
let mid = (left + right) >> 1;
if(mid >= x)
build_tree(id << 1, left, mid, x);
else
build_tree(id << 1 | 1, mid + 1, right, x);
tree[id] = tree[id << 1] + tree[id << 1 | 1];
}
function find(id, minX, maxX, left, right) {
if(minX === left && maxX === right)
return tree[id];
let mid = (minX + maxX) >> 1;
if(right <= mid)
return find(id << 1, minX, mid, left, right);
else if(left > mid)
return find(id << 1 | 1, mid + 1, maxX, left, right);
else
return find(id << 1, minX, mid, left, mid) + find(id << 1 | 1, mid + 1, maxX, mid + 1, right);
}
这里用个简单的例子来讲解一下这段代码,[[1,1], [2,2], [3,1]],首先我是遍历一遍点去拿到x的边界值即maxn=3,也就是说,我们需要维护的就是一个[0,3]的区间,该树如下图所示

小结
线段树虽然能节省时间上的开支,但是也是建立在了开出很多数组的前提下,是一种以空间换时间的解决方案,其实涉及区间和,单点更新的题也可以用树状数组来做,时间复杂度和线段树一样,但是空间上能省出很多来,有兴趣的可以自己去了解一下,本题也可以用树状数组解决