2025 A卷 100分 题型
本文涵盖详细的问题分析、解题思路、代码实现、代码详解、测试用例以及综合分析;
并提供Java、python、JavaScript、C++、C语言、GO六种语言的最佳实现方式!
本文收录于专栏:《2025华为OD真题目录+全流程解析/备考攻略/经验分享》
华为OD机试真题《构成正方形的数量》:
目录
题目名称:构成正方形的数量
- 知识点:几何算法、逻辑处理
- 时间限制:1秒
- 空间限制:256MB
- 限定语言:不限
题目描述
输入 N 个互不相同的二维整数坐标,求这 N 个坐标可以构成的正方形数量。(若两个向量的内积为零,则这两个向量垂直)
输入描述
- 第一行为正整数 N,表示坐标数量(1 ≤ N ≤ 100)。
- 后续 N 行每行为坐标 x y,以空格分隔,x、y均为整数(-10 ≤ x, y ≤ 10)。
输出描述
- 输出可构成的正方形数量。
示例1
输入:
3
1 3
2 4
3 1
输出:
0
说明:3个点无法构成正方形。
示例2
输入:
4
0 0
1 2
3 1
2 -1
输出:
1
说明:4个点可构成一个正方形。
Java
问题分析
我们需要根据输入的N个二维坐标点,计算能构成的正方形数量。正方形的判定条件是四个点满足特定的几何条件:四条边长度相等,相邻边垂直。
解题思路
- 输入处理:读取所有坐标点,并存入集合以便快速查找。
- 遍历所有点对:对于每两个点,计算可能的另外两个点是否存在。
- 几何条件验证:通过向量旋转确定可能的另外两个点,并检查是否存在。
- 去重处理:将找到的正方形的四个点排序后生成唯一标识,避免重复统计。
代码实现
import java.util.*;
class Point {
int x, y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
// 重写equals和hashCode方法,确保正确比较点
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Point point = (Point) o;
return x == point.x && y == point.y;
}
@Override
public int hashCode() {
return Objects.hash(x, y);
}
}
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
List<Point> points = new ArrayList<>();
Set<Point> pointSet = new HashSet<>();
// 读取所有点并存入集合
for (int i = 0; i < n; i++) {
int x = scanner.nextInt();
int y = scanner.nextInt();
Point p = new Point(x, y);
points.add(p);
pointSet.add(p);
}
Set<String> squares = new HashSet<>();
// 遍历所有点对
for (int i = 0; i < points.size(); i++) {
Point p1 = points.get(i);
for (int j = 0; j < points.size(); j++) {
if (i == j) continue; // 跳过同一个点
Point p2 = points.get(j);
// 计算向量差
int dx = p2.x - p1.x;
int dy = p2.y - p1.y;
// 情况1:计算可能的另外两个点
Point p3 = new Point(p2.x - dy, p2.y + dx);
Point p4 = new Point(p1.x - dy, p1.y + dx);
// 检查这两个点是否存在
if (pointSet.contains(p3) && pointSet.contains(p4)) {
addSquare(squares, p1, p2, p3, p4);
}
// 情况2:另一个方向
Point p5 = new Point(p2.x + dy, p2.y - dx);
Point p6 = new Point(p1.x + dy, p1.y - dx);
if (pointSet.contains(p5) && pointSet.contains(p6)) {
addSquare(squares, p1, p2, p5, p6);
}
}
}
System.out.println(squares.size());
}
// 生成正方形的唯一键并存入集合
private static void addSquare(Set<String> squares, Point... points) {
List<Point> list = new ArrayList<>(Arrays.asList(points));
// 按x和y排序,生成唯一键
Collections.sort(list, (a, b) -> {
if (a.x != b.x) return a.x - b.x;
return a.y - b.y;
});
StringBuilder key = new StringBuilder();
for (Point p : list) {
key.append(p.x).append(',').append(p.y).append(';');
}
squares.add(key.toString());
}
}
代码详细解析
- Point类:封装点的坐标,重写
equals
和hashCode
以便正确比较。 - 输入处理:读取所有点并存入列表和集合,集合用于快速查找点是否存在。
- 遍历点对:双重循环遍历所有可能的点对,计算两个可能的另外两个点。
- 向量旋转:通过向量旋转计算另外两个点,检查它们是否存在于集合中。
- 唯一键生成:将四个点排序后生成字符串作为唯一标识,避免重复统计。
- 输出结果:集合的大小即为不同正方形的数量。
示例测试
示例1输入:
3
1 3
2 4
3 1
输出:
0
解析:三点无法构成正方形。
示例2输入:
4
0 0
1 2
3 1
2 -1
输出:
1
解析:四个点构成一个正方形。
示例3输入:
4
0 0
0 1
1 1
1 0
输出:
1
解析:四个点构成一个正方形。
综合分析
- 时间复杂度:O(N²),遍历所有点对的时间复杂度为O(N²),每次处理两个可能的正方形。
- 空间复杂度:O(N),存储点和集合的空间。
- 优势:通过向量旋转快速确定可能的点,利用集合去重确保统计正确。
- 适用场景:适用于坐标点数量适中的情况,高效且准确。
python
问题分析
给定 N 个二维坐标点,计算这些点能构成多少个不同的正方形。正方形的判定条件是四个点满足特定几何条件:所有边长相等且相邻边垂直。需注意点互不相同且坐标范围有限。
解题思路
- 输入处理:读取所有点,存储到列表和集合中,集合用于快速查找点是否存在。
- 遍历点对:对每两个点,计算可能构成正方形的另外两个点。
- 向量旋转:通过向量旋转确定可能的另外两个点位置,检查是否存在。
- 去重处理:将四个点排序后生成唯一标识,避免重复计数。
代码实现
n = int(input())
points = [tuple(map(int, input().split())) for _ in range(n)]
point_set = set(points)
squares = set()
for i in range(n):
for j in range(n):
if i == j:
continue # 跳过相同的点
p1 = points[i]
p2 = points[j]
dx = p2[0] - p1[0]
dy = p2[1] - p1[1]
# 计算两种可能的另外两个点
# 情况1:顺时针旋转后的点
p3 = (p2[0] - dy, p2[1] + dx)
p4 = (p1[0] - dy, p1[1] + dx)
if p3 in point_set and p4 in point_set:
square = tuple(sorted([p1, p2, p3, p4]))
squares.add(square)
# 情况2:逆时针旋转后的点
p5 = (p2[0] + dy, p2[1] - dx)
p6 = (p1[0] + dy, p1[1] - dx)
if p5 in point_set and p6 in point_set:
square = tuple(sorted([p1, p2, p5, p6]))
squares.add(square)
print(len(squares))
代码详细解析
-
输入处理:
n = int(input())
:读取点的数量。points = [...]
:读取所有点的坐标并存入列表。point_set = set(points)
:将点存入集合以便快速查找。
-
遍历点对:
- 双重循环遍历所有点对
(i, j)
,跳过i == j
的情况。 dx
和dy
计算两点间的向量差。
- 双重循环遍历所有点对
-
向量旋转计算:
- 顺时针旋转:根据向量
(dx, dy)
旋转 90 度后的坐标公式计算p3
和p4
。 - 逆时针旋转:根据向量
(dx, dy)
旋转 -90 度后的坐标公式计算p5
和p6
。
- 顺时针旋转:根据向量
-
检查点存在性:
- 若
p3
和p4
(或p5
和p6
)均存在于集合中,则这四个点构成正方形。
- 若
-
去重处理:
sorted([p1, p2, p3, p4])
:将四个点按坐标排序,生成唯一标识。- 将排序后的元组存入集合
squares
,自动去重。
-
输出结果:
- 最终集合
squares
的大小即为不同正方形的数量。
- 最终集合
示例测试
示例1输入:
3
1 3
2 4
3 1
输出:
0
解析:三点无法构成正方形。
示例2输入:
4
0 0
1 2
3 1
2 -1
输出:
1
解析:四个点构成一个正方形。
示例3输入:
4
0 0
0 1
1 1
1 0
输出:
1
解析:四个点构成一个正方形。
综合分析
-
时间复杂度:O(N²)
- 双重循环遍历所有点对的时间复杂度为 O(N²),每对点计算两次可能的正方形。
-
空间复杂度:O(N)
- 存储所有点和集合的空间复杂度为 O(N)。
-
优势:
- 向量旋转法:通过几何计算快速确定正方形的其他点,避免复杂几何判断。
- 集合去重:利用排序后的元组唯一标识正方形,确保不重复计数。
-
适用场景:适用于坐标点数量适中(N ≤ 100)的场景,满足时间与空间要求。
JavaScript
问题分析
给定 N 个二维坐标点,计算这些点能构成多少个不同的正方形。正方形的判定条件是四个点满足特定几何条件:所有边长相等且相邻边垂直。需注意点互不相同且坐标范围有限。
解题思路
- 输入处理:读取所有点,存储到列表和集合中(集合用于快速查找点是否存在)。
- 遍历点对:对每两个点,计算可能构成正方形的另外两个点。
- 向量旋转:通过向量旋转确定可能的另外两个点位置,检查是否存在。
- 去重处理:将四个点排序后生成唯一标识,避免重复计数。
代码实现
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
terminal: false
});
let n;
const points = [];
const pointSet = new Set(); // 存储点坐标的字符串形式(用于快速判断是否存在)
const squares = new Set(); // 存储正方形的唯一标识(用于去重)
// 处理每一行输入
rl.on('line', (line) => {
if (!n) {
// 第一行输入为点的数量 N
n = parseInt(line.trim());
} else {
// 后续输入为坐标点,格式如 "1 2"
const [x, y] = line.trim().split(' ').map(Number);
points.push({ x, y });
pointSet.add(`${x},${y}`); // 将点转为字符串存入集合(如 "1,2")
// 当所有点输入完毕后开始处理
if (points.length === n) {
calculateSquares();
}
}
});
/**
* 计算所有可能的正方形
*/
function calculateSquares() {
// 遍历所有可能的点对组合(p1和p2)
for (let i = 0; i < points.length; i++) {
const p1 = points[i];
for (let j = 0; j < points.length; j++) {
if (i === j) continue; // 跳过同一个点
const p2 = points[j];
// 计算向量差(从p1到p2的向量)
const dx = p2.x - p1.x;
const dy = p2.y - p1.y;
// 顺时针旋转90度后的坐标公式
const p3 = { x: p2.x - dy, y: p2.y + dx };
const p4 = { x: p1.x - dy, y: p1.y + dx };
checkAndAddSquare(p1, p2, p3, p4);
// 逆时针旋转90度后的坐标公式
const p5 = { x: p2.x + dy, y: p2.y - dx };
const p6 = { x: p1.x + dy, y: p1.y - dx };
checkAndAddSquare(p1, p2, p5, p6);
}
}
// 输出结果
console.log(squares.size);
}
/**
* 检查点是否存在,若存在则生成正方形的唯一标识
* @param {Object} p1 - 点1坐标 {x, y}
* @param {Object} p2 - 点2坐标 {x, y}
* @param {Object} p3 - 计算出的候选点3
* @param {Object} p4 - 计算出的候选点4
*/
function checkAndAddSquare(p1, p2, p3, p4) {
// 检查候选点是否存在于集合中
if (
pointSet.has(`${p3.x},${p3.y}`) &&
pointSet.has(`${p4.x},${p4.y}`)
) {
// 将四个点按坐标排序(生成唯一标识)
const sortedPoints = [
`${p1.x},${p1.y}`,
`${p2.x},${p2.y}`,
`${p3.x},${p3.y}`,
`${p4.x},${p4.y}`
].sort();
// 拼接成字符串作为唯一标识(如 "0,0;0,1;1,0;1,1;")
const squareKey = sortedPoints.join(';');
squares.add(squareKey);
}
}
代码详细解析
** 1. 输入处理模块**
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
terminal: false
});
- 使用 Node.js 的
readline
模块逐行读取输入。 terminal: false
表示关闭终端模式,避免输出被干扰。
2. 数据结构初始化
let n;
const points = [];
const pointSet = new Set();
const squares = new Set();
n
:存储点的数量。points
:数组存储所有点的坐标对象(如{x: 1, y: 2}
)。pointSet
:集合存储点的字符串形式(如"1,2"
),用于快速判断点是否存在。squares
:集合存储正方形的唯一标识,用于自动去重。
3. 核心逻辑
function calculateSquares() {
for (let i = 0; i < points.length; i++) {
const p1 = points[i];
for (let j = 0; j < points.length; j++) {
// ...遍历点对并计算候选点
}
}
console.log(squares.size);
}
- 双重循环:遍历所有可能的点对
(p1, p2)
,时间复杂度为 O(N²)。 - 向量旋转:通过几何公式计算另外两个候选点(详见下文)。
4. 向量旋转公式
// 顺时针旋转后的坐标公式
const p3 = { x: p2.x - dy, y: p2.y + dx };
const p4 = { x: p1.x - dy, y: p1.y + dx };
// 逆时针旋转后的坐标公式
const p5 = { x: p2.x + dy, y: p2.y - dx };
const p6 = { x: p1.x + dy, y: p1.y - dx };
- 数学原理:若向量
(dx, dy)
旋转 90 度,新向量为(-dy, dx)
。 - 图形解释:假设
p1
和p2
是正方形的一条边,通过旋转向量找到另外两个顶点。
5. 去重逻辑
const sortedPoints = [
`${p1.x},${p1.y}`,
`${p2.x},${p2.y}`,
`${p3.x},${p3.y}`,
`${p4.x},${p4.y}`
].sort();
- 排序:将四个点按字符串排序,确保不同的点对组合生成相同的标识。
- 唯一标识:拼接成字符串如
"0,0;0,1;1,0;1,1;"
,存入集合自动去重。
示例测试
示例1输入
3
1 3
2 4
3 1
- 输出:
0
- 解析:三点无法构成正方形。
示例2输入
4
0 0
1 2
3 1
2 -1
- 输出:
1
- 解析:四个点构成一个正方形。
示例3输入
4
0 0
0 1
1 1
1 0
- 输出:
1
- 解析:经典的正方形坐标。
综合分析
-
时间复杂度:O(N²)
- 双重循环遍历所有点对的时间复杂度为 O(N²),每个点对计算两次可能的正方形。
- 适用于 N ≤ 100 的场景,最大计算量为 100² = 10,000 次。
-
空间复杂度:O(N)
- 存储所有点和集合的空间复杂度为 O(N)。
-
优势:
- 向量旋转法:通过几何公式快速确定候选点,避免复杂的几何验证。
- 自动去重:利用集合的特性,简化重复正方形的过滤逻辑。
-
适用场景:
- 坐标点数量适中(N ≤ 100)。
- 需要快速判断点是否存在的场景(如棋盘游戏、图像识别)。
C++
问题分析
给定N个二维坐标点,计算这些点能构成多少个不同的正方形。正方形的判定条件是四个点满足特定几何条件:所有边长相等且相邻边垂直。需注意点互不相同且坐标范围有限。
解题思路
- 输入处理:读取所有点,存储到集合中以便快速查找。
- 遍历点对:对每两个点,计算可能构成正方形的另外两个点。
- 向量旋转:通过向量旋转确定可能的另外两个点位置,检查是否存在。
- 去重处理:将四个点排序后生成唯一标识,避免重复计数。
代码实现
#include <iostream>
#include <vector>
#include <unordered_set>
#include <algorithm>
#include <sstream>
using namespace std;
// 生成点的字符串表示(如 "1,2")
string pointToString(int x, int y) {
return to_string(x) + "," + to_string(y);
}
int main() {
int N;
cin >> N;
vector<pair<int, int>> points; // 存储所有点
unordered_set<string> pointSet; // 快速判断点是否存在
// 读取输入并初始化集合
for (int i = 0; i < N; ++i) {
int x, y;
cin >> x >> y;
points.push_back({x, y});
pointSet.insert(pointToString(x, y));
}
unordered_set<string> squares; // 存储正方形的唯一标识
// 遍历所有点对 (p1, p2)
for (int i = 0; i < N; ++i) {
auto& p1 = points[i];
for (int j = 0; j < N; ++j) {
if (i == j) continue; // 跳过同一个点
auto& p2 = points[j];
int dx = p2.first - p1.first; // 向量差 (dx, dy)
int dy = p2.second - p1.second;
// 计算顺时针旋转后的两个候选点
int p3x = p2.first - dy, p3y = p2.second + dx;
int p4x = p1.first - dy, p4y = p1.second + dx;
string p3Str = pointToString(p3x, p3y);
string p4Str = pointToString(p4x, p4y);
// 检查候选点是否存在
if (pointSet.count(p3Str) && pointSet.count(p4Str)) {
// 将四个点排序并生成唯一键
vector<pair<int, int>> square = {p1, p2, {p3x, p3y}, {p4x, p4y}};
sort(square.begin(), square.end());
ostringstream oss;
for (auto& p : square) {
oss << p.first << "," << p.second << ";";
}
squares.insert(oss.str());
}
// 计算逆时针旋转后的两个候选点
int p5x = p2.first + dy, p5y = p2.second - dx;
int p6x = p1.first + dy, p6y = p1.second - dx;
string p5Str = pointToString(p5x, p5y);
string p6Str = pointToString(p6x, p6y);
if (pointSet.count(p5Str) && pointSet.count(p6Str)) {
vector<pair<int, int>> square = {p1, p2, {p5x, p5y}, {p6x, p6y}};
sort(square.begin(), square.end());
ostringstream oss;
for (auto& p : square) {
oss << p.first << "," << p.second << ";";
}
squares.insert(oss.str());
}
}
}
// 输出正方形数量
cout << squares.size() << endl;
return 0;
}
代码详细解析
-
输入处理:
vector<pair<int, int>> points
:存储所有点的坐标。unordered_set<string> pointSet
:将每个点转为字符串(如"1,2"
)存入集合,用于快速查找。
-
遍历点对:
- 双重循环遍历所有点对
(p1, p2)
,时间复杂度为 O(N²)。 dx
和dy
表示从p1
到p2
的向量差。
- 双重循环遍历所有点对
-
向量旋转计算:
- 顺时针旋转:根据向量
(dx, dy)
旋转 90 度后的坐标公式计算候选点p3
和p4
。 - 逆时针旋转:根据向量
(dx, dy)
旋转 -90 度后的坐标公式计算候选点p5
和p6
。
- 顺时针旋转:根据向量
-
检查候选点存在性:
- 将候选点转为字符串后,检查是否存在于集合
pointSet
中。
- 将候选点转为字符串后,检查是否存在于集合
-
生成唯一标识:
- 将四个点存入
vector
并排序,生成形如"x1,y1;x2,y2;x3,y3;x4,y4;"
的字符串,存入集合squares
去重。
- 将四个点存入
-
输出结果:
- 集合
squares
的大小即为不同的正方形数量。
- 集合
示例测试
示例1输入:
3
1 3
2 4
3 1
输出:
0
解析:三点无法构成正方形。
示例2输入:
4
0 0
1 2
3 1
2 -1
输出:
1
解析:四个点构成一个正方形。
示例3输入:
4
0 0
0 1
1 1
1 0
输出:
1
解析:经典的正方形坐标。
综合分析
-
时间复杂度:O(N²)
- 双重循环遍历所有点对的时间复杂度为 O(N²),每个点对计算两次可能的正方形。
- 适用于 N ≤ 100 的场景,最大计算量为 100² = 10,000 次。
-
空间复杂度:O(N)
- 存储所有点和集合的空间复杂度为 O(N)。
-
优势:
- 向量旋转法:通过几何公式快速确定候选点,避免复杂的几何验证。
- 自动去重:利用集合的特性,简化重复正方形的过滤逻辑。
-
适用场景:
- 坐标点数量适中(N ≤ 100)。
- 需要快速判断点是否存在的场景(如棋盘游戏、图像识别)。
C语言
问题分析
给定N个二维坐标点,计算这些点能构成多少个不同的正方形。正方形的判定条件是四个点满足特定几何条件:所有边长相等且相邻边垂直。需注意点互不相同且坐标范围有限。
解题思路
- 输入处理:读取所有点并存储,同时建立哈希表快速查找点是否存在。
- 遍历点对:对每两个点,通过向量旋转计算可能的另外两个点。
- 存在性检查:验证计算出的点是否存在。
- 去重处理:将四个点排序后生成唯一标识,避免重复统计。
代码实现
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
int x;
int y;
} Point;
int hash[21][21] = {0}; // 坐标范围-10到10,转换为0到20的索引
// 比较函数用于排序点
int comparePoints(const void *a, const void *b) {
Point *p1 = (Point*)a;
Point *p2 = (Point*)b;
if (p1->x != p2->x) return p1->x - p2->x;
return p1->y - p2->y;
}
// 检查点是否有效(在范围内且存在)
int isPointValid(Point p) {
if (p.x < -10 || p.x > 10 || p.y < -10 || p.y > 10) return 0;
return hash[p.x + 10][p.y + 10];
}
// 动态数组存储正方形键
char **squares = NULL;
int squareCount = 0;
// 生成唯一键并去重
void addSquare(Point p1, Point p2, Point p3, Point p4) {
Point arr[] = {p1, p2, p3, p4};
qsort(arr, 4, sizeof(Point), comparePoints); // 排序
// 生成唯一字符串键
char key[100];
snprintf(key, sizeof(key), "%d,%d;%d,%d;%d,%d;%d,%d;",
arr[0].x, arr[0].y, arr[1].x, arr[1].y,
arr[2].x, arr[2].y, arr[3].x, arr[3].y);
// 检查是否已存在
for (int i = 0; i < squareCount; i++) {
if (strcmp(squares[i], key) == 0) return;
}
// 添加到数组
char *newKey = strdup(key);
squares = realloc(squares, (squareCount + 1) * sizeof(char*));
squares[squareCount++] = newKey;
}
int main() {
int n;
scanf("%d", &n);
Point points[100];
// 读取点并初始化哈希表
for (int i = 0; i < n; i++) {
int x, y;
scanf("%d %d", &x, &y);
points[i].x = x;
points[i].y = y;
hash[x + 10][y + 10] = 1; // 标记存在
}
// 遍历所有点对
for (int i = 0; i < n; i++) {
Point p1 = points[i];
for (int j = 0; j < n; j++) {
if (i == j) continue;
Point p2 = points[j];
int dx = p2.x - p1.x;
int dy = p2.y - p1.y;
// 顺时针旋转后的两个点
Point p3 = {p2.x - dy, p2.y + dx};
Point p4 = {p1.x - dy, p1.y + dx};
if (isPointValid(p3) && isPointValid(p4))
addSquare(p1, p2, p3, p4);
// 逆时针旋转后的两个点
Point p5 = {p2.x + dy, p2.y - dx};
Point p6 = {p1.x + dy, p1.y - dx};
if (isPointValid(p5) && isPointValid(p6))
addSquare(p1, p2, p5, p6);
}
}
printf("%d\n", squareCount);
// 释放内存
for (int i = 0; i < squareCount; i++) free(squares[i]);
free(squares);
return 0;
}
代码详细解析
-
数据结构定义:
Point
结构体存储坐标。hash
二维数组标记点是否存在,坐标范围-10到10转换为0到20的索引。
-
比较函数:
comparePoints
:按x从小到大排序,x相同按y排序,确保排序后的点顺序一致。
-
有效性检查:
isPointValid
:检查坐标是否在范围内,并通过哈希表判断点是否存在。
-
添加正方形:
addSquare
:将四个点排序后生成唯一字符串键,若不存在则添加到动态数组。
-
主函数流程:
- 读取输入并初始化哈希表。
- 双重循环遍历所有点对,计算可能的正方形顶点。
- 检查顶点存在性并添加唯一键。
- 输出结果并释放内存。
示例测试
示例1输入:
3
1 3
2 4
3 1
输出:
0
解析:三个点无法构成正方形。
示例2输入:
4
0 0
1 2
3 1
2 -1
输出:
1
解析:四个点构成一个正方形。
示例3输入:
4
0 0
0 1
1 1
1 0
输出:
1
解析:经典正方形。
综合分析
-
时间复杂度:O(N²)
- 遍历所有点对的时间复杂度为O(N²),每个点对生成两次可能的正方形。
-
空间复杂度:O(N)
- 哈希表空间固定为21x21,动态数组存储的正方形数量有限。
-
优势:
- 快速查找:哈希表实现O(1)时间检查点是否存在。
- 严格去重:排序后生成唯一键,确保不同点对生成的同一正方形只计数一次。
-
适用场景:适用于坐标点数量适中(N ≤ 100)的场景,如棋盘游戏、几何图形识别。
GO
问题分析
给定 N 个二维坐标点,计算这些点能构成多少个不同的正方形。正方形的判定条件是四个点满足特定几何条件:所有边长相等且相邻边垂直。需注意点互不相同且坐标范围有限。
解题思路
- 输入处理:读取所有点,存储到切片和哈希集合中以便快速查找。
- 遍历点对:对每两个点,通过向量旋转计算可能的另外两个点。
- 存在性检查:验证计算出的点是否存在于集合中。
- 去重处理:将四个点排序后生成唯一标识,确保每个正方形只计数一次。
代码实现
package main
import (
"bufio"
"fmt"
"os"
"sort"
"strconv"
"strings"
)
// Point 结构体表示二维坐标点
type Point struct {
X, Y int
}
// Points 类型用于排序
type Points []Point
// 实现 sort.Interface 接口
func (p Points) Len() int { return len(p) }
func (p Points) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
func (p Points) Less(i, j int) bool {
if p[i].X == p[j].X {
return p[i].Y < p[j].Y
}
return p[i].X < p[j].X
}
// 生成点的唯一字符串标识
func pointKey(p Point) string {
return fmt.Sprintf("%d,%d", p.X, p.Y)
}
func main() {
scanner := bufio.NewScanner(os.Stdin)
scanner.Scan()
n, _ := strconv.Atoi(scanner.Text())
points := make([]Point, 0, n)
pointSet := make(map[string]struct{}) // 哈希集合存储点,用于快速查找
// 读取所有点并初始化集合
for i := 0; i < n; i++ {
scanner.Scan()
line := scanner.Text()
parts := strings.Split(line, " ")
x, _ := strconv.Atoi(parts[0])
y, _ := strconv.Atoi(parts[1])
p := Point{X: x, Y: y}
points = append(points, p)
pointSet[pointKey(p)] = struct{}{}
}
squares := make(map[string]struct{}) // 存储正方形的唯一标识
// 遍历所有点对
for i := 0; i < len(points); i++ {
p1 := points[i]
for j := 0; j < len(points); j++ {
if i == j {
continue // 跳过同一个点
}
p2 := points[j]
dx := p2.X - p1.X // 向量差 (dx, dy)
dy := p2.Y - p1.Y
// 情况1:顺时针旋转后的点
p3 := Point{X: p2.X - dy, Y: p2.Y + dx}
p4 := Point{X: p1.X - dy, Y: p1.Y + dx}
if checkExists(p3, pointSet) && checkExists(p4, pointSet) {
addSquare(p1, p2, p3, p4, squares)
}
// 情况2:逆时针旋转后的点
p5 := Point{X: p2.X + dy, Y: p2.Y - dx}
p6 := Point{X: p1.X + dy, Y: p1.Y - dx}
if checkExists(p5, pointSet) && checkExists(p6, pointSet) {
addSquare(p1, p2, p5, p6, squares)
}
}
}
// 输出结果
fmt.Println(len(squares))
}
// 检查点是否存在
func checkExists(p Point, set map[string]struct{}) bool {
_, exists := set[pointKey(p)]
return exists
}
// 生成唯一标识并添加到集合
func addSquare(p1, p2, p3, p4 Point, squares map[string]struct{}) {
// 将四个点排序
points := Points{p1, p2, p3, p4}
sort.Sort(points)
// 生成唯一字符串键
var key strings.Builder
for _, p := range points {
key.WriteString(fmt.Sprintf("%d,%d;", p.X, p.Y))
}
squares[key.String()] = struct{}{}
}
代码详细解析
-
数据结构定义:
Point
结构体存储坐标点。Points
类型实现sort.Interface
,用于排序四个点。
-
输入处理:
- 读取输入,解析为
Point
切片,并将每个点转为字符串存入哈希集合pointSet
。
- 读取输入,解析为
-
遍历点对:
- 双重循环遍历所有点对
(p1, p2)
,计算两种可能的另外两个点(顺时针和逆时针旋转)。 dx
和dy
表示向量差。
- 双重循环遍历所有点对
-
存在性检查:
checkExists
函数检查候选点是否在集合中。
-
去重处理:
addSquare
将四个点排序后生成唯一字符串键,存入squares
集合。
-
输出结果:
squares
集合的大小即为不同正方形的数量。
示例测试
示例1输入:
3
1 3
2 4
3 1
输出:
0
解析:三点无法构成正方形。
示例2输入:
4
0 0
1 2
3 1
2 -1
输出:
1
解析:四个点构成一个正方形。
示例3输入:
4
0 0
0 1
1 1
1 0
输出:
1
解析:经典正方形。
综合分析
-
时间复杂度:O(N²)
- 双重循环遍历所有点对的时间复杂度为 O(N²),每个点对生成两次可能的正方形。
-
空间复杂度:O(N)
- 哈希集合存储所有点的空间复杂度为 O(N)。
-
优势:
- 向量旋转法:通过几何公式快速确定候选点,避免复杂几何验证。
- 严格去重:排序后生成唯一键,确保不同点对生成的同一正方形只计数一次。
-
适用场景:
- 坐标点数量适中(N ≤ 100),如棋盘游戏、几何图形识别。
更多内容:
https://www.kdocs.cn/l/cvk0eoGYucWA
本文发表于【纪元A梦】,关注我,获取更多实用教程/资源!