2025 A卷 100分 题型
本文涵盖详细的问题分析、解题思路、代码实现、代码详解、测试用例以及综合分析;
并提供Java、python、JavaScript、C++、C语言、GO六种语言的最佳实现方式!
本文收录于专栏:《2025华为OD真题目录+全流程解析/备考攻略/经验分享》
华为OD机试真题《构成正方形的数量》:
输入 N 个互不相同的二维整数坐标,求这 N 个坐标可以构成的正方形数量。(若两个向量的内积为零,则这两个向量垂直)
输入描述
输出描述
示例1
输入:
3
1 3
2 4
3 1
输出:
0
说明:3个点无法构成正方形。
示例2
输入:
4
0 0
1 2
3 1
2 -1
输出:
1
说明:4个点可构成一个正方形。
我们需要根据输入的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());
}
}
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
解析:四个点构成一个正方形。
给定 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)
优势:
适用场景:适用于坐标点数量适中(N ≤ 100)的场景,满足时间与空间要求。
给定 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
});
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个二维坐标点,计算这些点能构成多少个不同的正方形。正方形的判定条件是四个点满足特定几何条件:所有边长相等且相邻边垂直。需注意点互不相同且坐标范围有限。
#include
#include
#include
#include
#include
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> points
:存储所有点的坐标。unordered_set 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个二维坐标点,计算这些点能构成多少个不同的正方形。正方形的判定条件是四个点满足特定几何条件:所有边长相等且相邻边垂直。需注意点互不相同且坐标范围有限。
#include
#include
#include
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)
优势:
适用场景:适用于坐标点数量适中(N ≤ 100)的场景,如棋盘游戏、几何图形识别。
给定 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)
优势:
适用场景:
https://www.kdocs.cn/l/cvk0eoGYucWA
本文发表于【纪元A梦】,关注我,获取更多实用教程/资源!