难度中等
给你一个在 X-Y 平面上的点构成的数据流。设计一个满足下述要求的算法:
- 添加 一个在数据流中的新点到某个数据结构中。可以添加 重复 的点,并会视作不同的点进行处理。
- 给你一个查询点,请你从数据结构中选出三个点,使这三个点和查询点一同构成一个 面积为正 的 轴对齐正方形 ,统计 满足该要求的方案数目。
轴对齐正方形 是一个正方形,除四条边长度相同外,还满足每条边都与 x-轴 或 y-轴 平行或垂直。
实现 DetectSquares
类:
DetectSquares()
使用空数据结构初始化对象void add(int[] point)
向数据结构添加一个新的点point = [x, y]
int count(int[] point)
统计按上述方式与点point = [x, y]
共同构造 轴对齐正方形 的方案数。
示例:
输入: ["DetectSquares", "add", "add", "add", "count", "count", "add", "count"] [[], [[3, 10]], [[11, 2]], [[3, 2]], [[11, 10]], [[14, 8]], [[11, 2]], [[11, 10]]] 输出: [null, null, null, null, 1, 0, null, 2] 解释: DetectSquares detectSquares = new DetectSquares(); detectSquares.add([3, 10]); detectSquares.add([11, 2]); detectSquares.add([3, 2]); detectSquares.count([11, 10]); // 返回 1 。你可以选择: // - 第一个,第二个,和第三个点 detectSquares.count([14, 8]); // 返回 0 。查询点无法与数据结构中的这些点构成正方形。 detectSquares.add([11, 2]); // 允许添加重复的点。 detectSquares.count([11, 10]); // 返回 2 。你可以选择: // - 第一个,第二个,和第三个点 // - 第一个,第三个,和第四个点
提示:
point.length == 2
0 <= x, y <= 1000
- 调用
add
和count
的 总次数 最多为5000
思路:本题需要构建一个适合题意的数据结构。
先看第一个需求:添加 一个在数据流中的新点到某个数据结构中。可以添加 重复 的点,并会视作不同的点进行处理
从数据范围为看,0 <= x, y <= 1000,我们是可以选择开一个int[1000 + 5][1000 + 5]的数组的。
但先别急,我们来看第二个需求:给你一个查询点,请你从数据结构中选出三个点,使这三个点和查询点一同构成一个 面积为正 的 轴对齐正方形 ,统计 满足该要求的方案数目。
我们要怎么解决这个需求呢?想要确定一个正方形
A.可以选择确定边长和边的方向(上下左右[因为本题要求正方形的边平行于X-Y轴]),再加上一个给定点即可。
这种方案下,用int[1000 + 5][1000 + 5]来标记int[j][j]位置的点的个数是很方便的,因为可以利用给定的点和边长直接计算出来顶点的位置。然而麻烦的地方在于,边长是需要枚举的,同时还有左上左下右上右下四个位置的枚举,总的枚举复杂度为O(1000*4)。
class DetectSquares {
public:
int points_num[1000 + 5][1000 + 5];
DetectSquares() {
memset(points_num, 0, sizeof(points_num));
}
void add(vector<int> point) {
++ points_num[point[0]][point[1]];
}
int count(vector<int> point) {
int square_len, lx, ly, rx, ry, square_num = 0, dir;
for(square_len = 1; square_len <= 1000; ++ square_len){
for(dir = 0; dir < 4; ++ dir){
switch(dir){
case 0://0.向左下,即当前点作为右上顶点
rx = point[0]; ry = point[1];
lx = rx - square_len; ly = ry - square_len;
if(lx >= 0 && ly >= 0){
square_num += points_num[lx][ly] * points_num[lx][ry] * points_num[rx][ly];
}
break;
case 1://1.向左上,即当前点作为右下顶点
rx = point[0]; ly = point[1];
lx = rx - square_len; ry = ly + square_len;
if(lx >= 0 && ry <= 1000){
square_num += points_num[lx][ly] * points_num[rx][ry] * points_num[lx][ry];
}
break;
case 2://2.向右下,即当前点作为左上顶点
lx = point[0]; ry = point[1];
rx = lx + square_len; ly = ry - square_len;
if(rx <= 1000 && ly >= 0){
square_num += points_num[lx][ly] * points_num[rx][ry] * points_num[rx][ly];
}
break;
case 3: //3.向右上,即当前点为左下顶点
lx = point[0]; ly = point[1];
rx = lx + square_len; ry = ly + square_len;
if(rx <= 1000 && ry <= 1000){
square_num += points_num[rx][ry] * points_num[lx][ry] * points_num[rx][ly];
}
break;
default:break;
}
}
}
return square_num;
}
};
[注]:1)上述代码中rx,ry代表的是右上角的x、y坐标,lx,ly是左下角的x、y坐标。实际上我们可以有别的方式将代码进行统一,但是这样会失去可读性,也不容易排查bug。
2)使用乘法做结果累计是因为题目中已经明说,同一个位置上的多个点算做不同的点,因此适用于组合问题的乘法规则。
3)本题使用int[][]数组存储点信息,反而造成了枚举边长时的困难。
B.可以在与给定点同行或者同列的位置上选择一个点(计算该点与给定点之间的距离就得到了边长)+边的方向(本题已经说明构成的正方形的两边分别平行于X-Y轴,并且count函数会给出一个点,那么只需要额外再确定2个点就可以确定下正方形),
转换思路-由于本题中已经明说
0 <= x, y <= 1000
- 调用
add
和count
的 总次数 最多为5000
也就是说,在我们这个1000*1000的空间上,点的分布离散程度很大,此时在存储时我们就没有必要开这么大的数据,可以使用嵌套哈希的结构。
用martix_rows[i]来表示第i行,martix_rows[i][j]就等于上面的int[i][j],但由于点是离散的,因此martix_rows[i]中可能并没有存储0-1000列。
算法调整如下:给定点 (x,y)
我们只需要枚举martix_rows[x]中的所有元素,针对每一个元素都可以得到一个边长,此时按照上面的逻辑来即可。
这个思路可以利用数据离散分布的情况降低枚举边长的时间耗费。