1. 问题分析与算法设计
在二维坐标系中给定N个互不相同的点,我们需要计算这些点能够构成多少个不同的正方形。这个问题看似简单,但直接暴力枚举所有可能的四个点组合显然效率太低(时间复杂度O(n^4)),对于N=100的情况会有近百万次组合检查。
1.1 数学原理与优化思路
正方形的几何特性为我们提供了优化方向。一个正方形可以由其任意一条边唯一确定,具体来说:
- 已知两个相邻顶点A(x1,y1)和B(x2,y2),可以通过向量运算推导出另外两个顶点C和D的坐标
- 推导公式基于向量旋转和向量加减:
- 向量AB = (x2-x1, y2-y1)
- 将AB逆时针旋转90度得到AD = (y1-y2, x2-x1)
- 因此D点坐标为 (x1 + (y1-y2), y1 + (x2-x1))
- C点坐标为 (x2 + (y1-y2), y2 + (x2-x1))
这个数学原理将问题转化为:遍历所有点对,计算可能构成正方形的另外两个点,检查它们是否存在于输入点集中。
1.2 算法复杂度分析
优化后的算法时间复杂度:
- 遍历所有点对:O(n²)
- 对每个点对计算两个可能的方向:O(1)
- 检查计算出的点是否存在:使用哈希集合可实现O(1)查询
总时间复杂度为O(n²),相比暴力解法有了质的提升,完全可以处理n=100的情况。
2. 代码实现详解
2.1 JavaScript实现解析
javascript复制function getSquareCount(coordinates) {
let squareCount = 0;
const set = new Set(coordinates);
for (let i = 0; i < coordinates.length; i++) {
let [x1, y1] = coordinates[i].split(" ").map(Number);
for (let j = i + 1; j < coordinates.length; j++) {
let [x2, y2] = coordinates[j].split(" ").map(Number);
// 第一种方向的正方形
let x3 = x1 - (y1 - y2);
let y3 = y1 + (x1 - x2);
let x4 = x2 - (y1 - y2);
let y4 = y2 + (x1 - x2);
if (set.has(`${x3} ${y3}`) && set.has(`${x4} ${y4}`)) squareCount++;
// 第二种方向的正方形
let x5 = x1 + (y1 - y2);
let y5 = y1 - (x1 - x2);
let x6 = x2 + (y1 - y2);
let y6 = y2 - (x1 - x2);
if (set.has(`${x5} ${y5}`) && set.has(`${x6} ${y6}`)) squareCount++;
}
}
return squareCount / 4;
}
关键点说明:
- 使用Set存储所有坐标实现O(1)查询
- 双重循环遍历所有不重复的点对(i,j)
- 对每个点对计算两种可能方向的正方形顶点
- 最后结果除以4消除重复计数
2.2 Java实现注意事项
java复制public static int getResult(int n, String[] coordinates) {
int squareCount = 0;
Set<String> set = new HashSet<>(Arrays.asList(coordinates));
for (int i = 0; i < n; i++) {
String[] parts1 = coordinates[i].split(" ");
int x1 = Integer.parseInt(parts1[0]);
int y1 = Integer.parseInt(parts1[1]);
for (int j = i + 1; j < n; j++) {
String[] parts2 = coordinates[j].split(" ");
int x2 = Integer.parseInt(parts2[0]);
int y2 = Integer.parseInt(parts2[1]);
// 第一种方向
int x3 = x1 - (y1 - y2);
int y3 = y1 + (x1 - x2);
int x4 = x2 - (y1 - y2);
int y4 = y2 + (x1 - x2);
if (set.contains(x3 + " " + y3) && set.contains(x4 + " " + y4)) squareCount++;
// 第二种方向
int x5 = x1 + (y1 - y2);
int y5 = y1 - (x1 - x2);
int x6 = x2 + (y1 - y2);
int y6 = y2 - (x1 - x2);
if (set.contains(x5 + " " + y5) && set.contains(x6 + " " + y6)) squareCount++;
}
}
return squareCount / 4;
}
Java实现要点:
- 使用HashSet存储坐标提高查询效率
- 注意Java中字符串分割和类型转换的写法
- 整数运算确保精度,避免浮点数问题
2.3 Python实现技巧
python复制def getResult():
squareCount = 0
coordinatesSet = set(coordinates)
for i in range(n):
x1, y1 = map(int, coordinates[i].split())
for j in range(i+1, n):
x2, y2 = map(int, coordinates[j].split())
# 第一种方向
x3 = x1 - (y1 - y2)
y3 = y1 + (x1 - x2)
x4 = x2 - (y1 - y2)
y4 = y2 + (x1 - x2)
if f"{x3} {y3}" in coordinatesSet and f"{x4} {y4}" in coordinatesSet:
squareCount += 1
# 第二种方向
x5 = x1 + (y1 - y2)
y5 = y1 - (x1 - x2)
x6 = x2 + (y1 - y2)
y6 = y2 - (x1 - x2)
if f"{x5} {y5}" in coordinatesSet and f"{x6} {y6}" in coordinatesSet:
squareCount += 1
return squareCount // 4
Python实现特点:
- 使用集合(set)实现高效成员检查
- map函数简化字符串到整数的转换
- f-string格式化坐标字符串
- 使用//进行整数除法
3. 算法正确性验证
3.1 为什么结果要除以4?
这个问题需要仔细理解。考虑一个具体的正方形ABCD:
- 当选择边AB时,会找到这个正方形
- 当选择边BC时,也会找到同一个正方形
- 选择边CD和DA时同样会找到
- 此外,选择对角线AC或BD时,也会找到这个正方形(因为算法会检查两个可能方向)
实际上,每个正方形会被算法计数8次(4条边×2个方向),但因为我们只遍历i<j的点对,所以减少到4次。因此需要将最终结果除以4。
3.2 边界情况测试
测试用例设计需要考虑以下情况:
- 点数不足4个时,结果应为0
- 所有点共线时,结果应为0
- 存在多个正方形时,确保都能正确识别
- 坐标包含负数和零的情况
- 有重复点的情况(根据题意输入保证无重复)
例如测试用例:
code复制4
0 0
0 1
1 0
1 1
应输出1,确实构成一个正方形。
4. 性能优化与扩展
4.1 进一步优化思路
虽然当前算法已经是O(n²),但对于更大的n仍可优化:
- 坐标哈希优化:将坐标转换为唯一整数,如(x,y)→(x*1000+y),提高哈希效率
- 并行计算:点对检查可以并行化处理
- 空间分区:将平面划分为网格,只检查相邻网格中的点对
4.2 问题扩展
类似问题可以扩展到:
- 三维空间中的立方体计数
- 其他几何图形计数(矩形、菱形等)
- 近似正方形计数(允许一定误差范围)
- 动态点集,支持添加/删除点后快速更新正方形数量
5. 实际应用场景
这种算法在实际中有多种应用:
- 计算机视觉中的形状识别
- 地理信息系统中的特征提取
- 游戏开发中的碰撞检测
- CAD软件中的几何约束求解
理解这种基于数学原理的算法设计方法,对于解决各类几何计算问题都有很大帮助。核心思想是将几何特性转化为可计算的数学表达式,再通过适当的数据结构实现高效查询。