华为OD机试真题——构成正方形的数量(2025A卷:100分)Java/python/JavaScript/C++/C/GO六种最佳实现

在这里插入图片描述

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个二维坐标点,计算能构成的正方形数量。正方形的判定条件是四个点满足特定的几何条件:四条边长度相等,相邻边垂直。

解题思路

  1. 输入处理:读取所有坐标点,并存入集合以便快速查找。
  2. 遍历所有点对:对于每两个点,计算可能的另外两个点是否存在。
  3. 几何条件验证:通过向量旋转确定可能的另外两个点,并检查是否存在。
  4. 去重处理:将找到的正方形的四个点排序后生成唯一标识,避免重复统计。

代码实现

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());
    }
}

代码详细解析

  1. Point类:封装点的坐标,重写equalshashCode以便正确比较。
  2. 输入处理:读取所有点并存入列表和集合,集合用于快速查找点是否存在。
  3. 遍历点对:双重循环遍历所有可能的点对,计算两个可能的另外两个点。
  4. 向量旋转:通过向量旋转计算另外两个点,检查它们是否存在于集合中。
  5. 唯一键生成:将四个点排序后生成字符串作为唯一标识,避免重复统计。
  6. 输出结果:集合的大小即为不同正方形的数量。

示例测试

示例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  

解析:四个点构成一个正方形。

综合分析

  1. 时间复杂度:O(N²),遍历所有点对的时间复杂度为O(N²),每次处理两个可能的正方形。
  2. 空间复杂度:O(N),存储点和集合的空间。
  3. 优势:通过向量旋转快速确定可能的点,利用集合去重确保统计正确。
  4. 适用场景:适用于坐标点数量适中的情况,高效且准确。

python

问题分析

给定 N 个二维坐标点,计算这些点能构成多少个不同的正方形。正方形的判定条件是四个点满足特定几何条件:所有边长相等且相邻边垂直。需注意点互不相同且坐标范围有限。


解题思路

  1. 输入处理:读取所有点,存储到列表和集合中,集合用于快速查找点是否存在。
  2. 遍历点对:对每两个点,计算可能构成正方形的另外两个点。
  3. 向量旋转:通过向量旋转确定可能的另外两个点位置,检查是否存在。
  4. 去重处理:将四个点排序后生成唯一标识,避免重复计数。

代码实现

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))

代码详细解析

  1. 输入处理

    • n = int(input()):读取点的数量。
    • points = [...]:读取所有点的坐标并存入列表。
    • point_set = set(points):将点存入集合以便快速查找。
  2. 遍历点对

    • 双重循环遍历所有点对 (i, j),跳过 i == j 的情况。
    • dxdy 计算两点间的向量差。
  3. 向量旋转计算

    • 顺时针旋转:根据向量 (dx, dy) 旋转 90 度后的坐标公式计算 p3p4
    • 逆时针旋转:根据向量 (dx, dy) 旋转 -90 度后的坐标公式计算 p5p6
  4. 检查点存在性

    • p3p4(或 p5p6)均存在于集合中,则这四个点构成正方形。
  5. 去重处理

    • sorted([p1, p2, p3, p4]):将四个点按坐标排序,生成唯一标识。
    • 将排序后的元组存入集合 squares,自动去重。
  6. 输出结果

    • 最终集合 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  

解析:四个点构成一个正方形。


综合分析

  1. 时间复杂度:O(N²)

    • 双重循环遍历所有点对的时间复杂度为 O(N²),每对点计算两次可能的正方形。
  2. 空间复杂度:O(N)

    • 存储所有点和集合的空间复杂度为 O(N)。
  3. 优势

    • 向量旋转法:通过几何计算快速确定正方形的其他点,避免复杂几何判断。
    • 集合去重:利用排序后的元组唯一标识正方形,确保不重复计数。
  4. 适用场景:适用于坐标点数量适中(N ≤ 100)的场景,满足时间与空间要求。


JavaScript

问题分析

给定 N 个二维坐标点,计算这些点能构成多少个不同的正方形。正方形的判定条件是四个点满足特定几何条件:所有边长相等且相邻边垂直。需注意点互不相同且坐标范围有限。


解题思路

  1. 输入处理:读取所有点,存储到列表和集合中(集合用于快速查找点是否存在)。
  2. 遍历点对:对每两个点,计算可能构成正方形的另外两个点。
  3. 向量旋转:通过向量旋转确定可能的另外两个点位置,检查是否存在。
  4. 去重处理:将四个点排序后生成唯一标识,避免重复计数。

代码实现

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)
  • 图形解释:假设 p1p2 是正方形的一条边,通过旋转向量找到另外两个顶点。

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
  • 解析:经典的正方形坐标。

综合分析

  1. 时间复杂度:O(N²)

    • 双重循环遍历所有点对的时间复杂度为 O(N²),每个点对计算两次可能的正方形。
    • 适用于 N ≤ 100 的场景,最大计算量为 100² = 10,000 次。
  2. 空间复杂度:O(N)

    • 存储所有点和集合的空间复杂度为 O(N)。
  3. 优势

    • 向量旋转法:通过几何公式快速确定候选点,避免复杂的几何验证。
    • 自动去重:利用集合的特性,简化重复正方形的过滤逻辑。
  4. 适用场景

    • 坐标点数量适中(N ≤ 100)。
    • 需要快速判断点是否存在的场景(如棋盘游戏、图像识别)。

C++

问题分析

给定N个二维坐标点,计算这些点能构成多少个不同的正方形。正方形的判定条件是四个点满足特定几何条件:所有边长相等且相邻边垂直。需注意点互不相同且坐标范围有限。


解题思路

  1. 输入处理:读取所有点,存储到集合中以便快速查找。
  2. 遍历点对:对每两个点,计算可能构成正方形的另外两个点。
  3. 向量旋转:通过向量旋转确定可能的另外两个点位置,检查是否存在。
  4. 去重处理:将四个点排序后生成唯一标识,避免重复计数。

代码实现

#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;
}

代码详细解析

  1. 输入处理

    • vector<pair<int, int>> points:存储所有点的坐标。
    • unordered_set<string> pointSet:将每个点转为字符串(如 "1,2")存入集合,用于快速查找。
  2. 遍历点对

    • 双重循环遍历所有点对 (p1, p2),时间复杂度为 O(N²)。
    • dxdy 表示从 p1p2 的向量差。
  3. 向量旋转计算

    • 顺时针旋转:根据向量 (dx, dy) 旋转 90 度后的坐标公式计算候选点 p3p4
    • 逆时针旋转:根据向量 (dx, dy) 旋转 -90 度后的坐标公式计算候选点 p5p6
  4. 检查候选点存在性

    • 将候选点转为字符串后,检查是否存在于集合 pointSet 中。
  5. 生成唯一标识

    • 将四个点存入 vector 并排序,生成形如 "x1,y1;x2,y2;x3,y3;x4,y4;" 的字符串,存入集合 squares 去重。
  6. 输出结果

    • 集合 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  

解析:经典的正方形坐标。


综合分析

  1. 时间复杂度:O(N²)

    • 双重循环遍历所有点对的时间复杂度为 O(N²),每个点对计算两次可能的正方形。
    • 适用于 N ≤ 100 的场景,最大计算量为 100² = 10,000 次。
  2. 空间复杂度:O(N)

    • 存储所有点和集合的空间复杂度为 O(N)。
  3. 优势

    • 向量旋转法:通过几何公式快速确定候选点,避免复杂的几何验证。
    • 自动去重:利用集合的特性,简化重复正方形的过滤逻辑。
  4. 适用场景

    • 坐标点数量适中(N ≤ 100)。
    • 需要快速判断点是否存在的场景(如棋盘游戏、图像识别)。

C语言

问题分析

给定N个二维坐标点,计算这些点能构成多少个不同的正方形。正方形的判定条件是四个点满足特定几何条件:所有边长相等且相邻边垂直。需注意点互不相同且坐标范围有限。


解题思路

  1. 输入处理:读取所有点并存储,同时建立哈希表快速查找点是否存在。
  2. 遍历点对:对每两个点,通过向量旋转计算可能的另外两个点。
  3. 存在性检查:验证计算出的点是否存在。
  4. 去重处理:将四个点排序后生成唯一标识,避免重复统计。

代码实现

#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;
}

代码详细解析

  1. 数据结构定义

    • Point结构体存储坐标。
    • hash二维数组标记点是否存在,坐标范围-10到10转换为0到20的索引。
  2. 比较函数

    • comparePoints:按x从小到大排序,x相同按y排序,确保排序后的点顺序一致。
  3. 有效性检查

    • isPointValid:检查坐标是否在范围内,并通过哈希表判断点是否存在。
  4. 添加正方形

    • addSquare:将四个点排序后生成唯一字符串键,若不存在则添加到动态数组。
  5. 主函数流程

    • 读取输入并初始化哈希表。
    • 双重循环遍历所有点对,计算可能的正方形顶点。
    • 检查顶点存在性并添加唯一键。
    • 输出结果并释放内存。

示例测试

示例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  

解析:经典正方形。


综合分析

  1. 时间复杂度:O(N²)

    • 遍历所有点对的时间复杂度为O(N²),每个点对生成两次可能的正方形。
  2. 空间复杂度:O(N)

    • 哈希表空间固定为21x21,动态数组存储的正方形数量有限。
  3. 优势

    • 快速查找:哈希表实现O(1)时间检查点是否存在。
    • 严格去重:排序后生成唯一键,确保不同点对生成的同一正方形只计数一次。
  4. 适用场景:适用于坐标点数量适中(N ≤ 100)的场景,如棋盘游戏、几何图形识别。


GO

问题分析

给定 N 个二维坐标点,计算这些点能构成多少个不同的正方形。正方形的判定条件是四个点满足特定几何条件:所有边长相等且相邻边垂直。需注意点互不相同且坐标范围有限。


解题思路

  1. 输入处理:读取所有点,存储到切片和哈希集合中以便快速查找。
  2. 遍历点对:对每两个点,通过向量旋转计算可能的另外两个点。
  3. 存在性检查:验证计算出的点是否存在于集合中。
  4. 去重处理:将四个点排序后生成唯一标识,确保每个正方形只计数一次。

代码实现

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{}{}
}

代码详细解析

  1. 数据结构定义

    • Point 结构体存储坐标点。
    • Points 类型实现 sort.Interface,用于排序四个点。
  2. 输入处理

    • 读取输入,解析为 Point 切片,并将每个点转为字符串存入哈希集合 pointSet
  3. 遍历点对

    • 双重循环遍历所有点对 (p1, p2),计算两种可能的另外两个点(顺时针和逆时针旋转)。
    • dxdy 表示向量差。
  4. 存在性检查

    • checkExists 函数检查候选点是否在集合中。
  5. 去重处理

    • addSquare 将四个点排序后生成唯一字符串键,存入 squares 集合。
  6. 输出结果

    • 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  

解析:经典正方形。


综合分析

  1. 时间复杂度:O(N²)

    • 双重循环遍历所有点对的时间复杂度为 O(N²),每个点对生成两次可能的正方形。
  2. 空间复杂度:O(N)

    • 哈希集合存储所有点的空间复杂度为 O(N)。
  3. 优势

    • 向量旋转法:通过几何公式快速确定候选点,避免复杂几何验证。
    • 严格去重:排序后生成唯一键,确保不同点对生成的同一正方形只计数一次。
  4. 适用场景

    • 坐标点数量适中(N ≤ 100),如棋盘游戏、几何图形识别。

更多内容:

https://www.kdocs.cn/l/cvk0eoGYucWA

本文发表于【纪元A梦】,关注我,获取更多实用教程/资源!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

纪元A梦

再小的支持也是一种动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值