【OD机试题解法笔记】构成正方形的数量

二维坐标构成正方形数量的算法解法

题目


输入N个互不相同的二维整数坐标,求这N个坐标可以构成的正方形数量。[内积为零的的两个向量垂直]

输入描述
第一行输入为N,N代表坐标数量,N为正整数。N <= 100

之后的 K 行输入为坐标x y以空格分隔,x,y为整数,-10<=x, y<=10

输出描述
输出可以构成的正方形数量。

用例

输入输出说明
3
1 3
2 4
3 1
03个点不足以构成正方形
4
0 0
1 2
3 1
2 -1
1

思考

正方形的对角线互相垂直,可以枚举每对坐标为一条对角线,然后旋转90度计算另一条对角线的2个端点坐标,判断计算的2个端点坐标是否存在于已知坐标集中,如果存在则找到一个正方形。由于正方形对角线有两条,枚举每条对角线最终会导致一个正方形被重复枚举2次,最终结果要除以2。

算法过程

1、根据输入把坐标存入List集合中,两重循环遍历List枚举对角线两个端点;

2、根据枚举的两个端点A和B计算AB中点Center和向量V(A或B减去center后把向量长度除以2);

3、将向量V旋转90度,顺时针或逆时针旋转都行,得到另一个向量V1,通过V1+Center和-V1+Center能计算另一条对角线上的两个端点;

二维旋转

假如我们要把向量 a 逆时针(旋转轴是从笛卡尔坐标系原点指向屏幕外面的轴,这个轴与屏幕垂直)旋转一个角度 φ 得到向量 b,如果 a 与x轴夹角是 α,长度 r = √(x_a^2 + x_b^2),则 x_a = r cosα, y_a = r sinα;

由三角恒等变换得到向量 b的坐标:

化简后:

将 a 旋转到得到 b 的转换写成矩阵形式:

观察旋转矩阵的每行有 sin^2 φ+cos^2 φ = 1,行与行之间存在 cos φ(− sin φ) + sin φ cos φ = 0,从而得到旋转矩阵是标准正交矩阵。旋转矩阵也可以看成一对标准正交的列向量或行向量。

回到题目中,把向量V逆时针旋转90度就是左乘一个旋转矩阵rotate(90):

\begin{bmatrix} 0 & -1\\ 1 & 0 \end{bmatrix} \cdot \begin{bmatrix} x\\ y \end{bmatrix} = \begin{bmatrix} -y\\ x \end{bmatrix}

3、根据AB的中点和向量V1计算端点CD,C = (A + B ) / 2 + V1/2, D = (A + B ) / 2 - V1/2;

4、判断坐标集中是否存在CD,存在则计数+1,最后将结果除以2得到正方形的数量。

参考代码

let cases = [
`3
1 3
2 4
3 1`,
`4
0 0
1 2
3 1
2 -1`
]

let ans = [
  0,
  1
];

let caseIndex = 0;
let lineIndex  = 0;

const readline = (function() {
  let lines = [];
  return function() {
    if (lineIndex === 0) {
      lines = cases[caseIndex].trim().split('\n').map(line => line.trim());
    }
    return lines[lineIndex++];
  }
}());

function solution() {
  const line = readline();
  let n = parseInt(line);
  const coordinates  = [];
  for (let i = 0; i < n; i++) {
    let [x, y] = readline().split(' ').map(e => parseInt(e));
    coordinates.push({x, y});
  }

  let count = 0;
  for (let i = 0; i < n; i++) {
    for (let j = i+1; j < n; j++) {
      let p1 = coordinates[i];
      let p2 = coordinates[j];
      let center = {x : (p1.x + p2.x) / 2, y: (p1.y + p2.y) / 2};
      let v1 = { x: p1.x-center.x, y: p1.y-center.y};
      let v2 = { x: -v1.y, y: v1.x }; // 逆时针旋转90度

      // 另一条对角线的2个端点
      let p3 = { x: center.x + v2.x, y: center.y + v2.y };
      let p4 = { x: center.x - v2.x, y: center.y - v2.y };

      // console.log('p1: ', p1);
      // console.log('p2: ', p2);
      // console.log('center: ', center);
      // console.log('v1: ', v1);
      // console.log('v2: ', v2);
      // console.log('p3: ', p3);
      // console.log('p4: ', p4);
      // console.log('---------------------');
     
      // 判断端点是否存在于已有的坐标集中
      if (coordinates.find(o => o.x === p3.x && o.y === p3.y) 
        && coordinates.find(o => o.x === p4.x && o.y === p4.y)) {
          count++;
       }
    }
  }
 
  // console.log("count = ", count/2);

  return count/2;
}

cases.forEach((_, i) => {
  caseIndex = i;
  lineIndex = 0;

  const result = solution();
  if (result === ans[i]) {
    console.log(true);
  } else {
    console.error(false);
  }
});

### 华为 OD 试题及解答 #### Java 题目解析 在华为 OD 考中,Java 是一种常见的编程语言。以下是一个关于二进制位操作的经典题目及其解决方案。 给定一个整数 `num`,计算其二进制表示中有多少个 `1` 的方法可以通过逐位检查实现[^2]。 以下是具体的代码示例: ```java import java.util.Scanner; public class Main { public static void main(String[] args) { Scanner in = new Scanner(System.in); int num = in.nextInt(); // 输入数字 int count = 0; // 计数器初始化 for (int i = 0; i < 32; i++) { // 假设输入的是标准的 32 位整数 if ((num & 1) == 1) { // 如果当前最低位是 1,则增加计数 count++; } num = num >>> 1; // 将数字无符号右移一位 } System.out.println(count); // 输出最终的结果 } } ``` 此程序的核心在于利用按位与运算符 (`&`) 和无符号右移运算符 (`>>>`) 来逐步提取并统计二进制中的每一位是否为 `1`。 --- #### 字符串处理题目解析 另一个常见问题是字符串的操作,比如将字符串中的每个单词首字母大写化[^3]。下面是一段 Python 实现的例子: ```python while True: try: words = input().split() # 获取用户输入并分割成列表 result = [] for word in words: if len(word) > 0: # 确保单词长度大于零 capitalized_word = f"{word[0].upper()}{word[1:]}" result.append(capitalized_word) print(" ".join(result)) # 打印结果 except Exception as e: break # 捕获异常后退出循环 ``` 该脚本通过遍历每一个单词,并将其第一个字符转为大写字母来完成任务。 --- #### 准备建议 为了更好地应对华为 OD 试,可以采取如下策略: - **熟悉常用算法**:掌握基本的数据结构(数组、链表、堆栈等)以及经典算法(排序、查找等),这些知识点经常会在实际测试中被考察到。 - **多练习编码能力**:针对不同类型的题目进行专项训练,尤其是时间复杂度优化方面的技巧学习。 - **理解业务场景需求**:部分试题可能结合具体应用场景设计,因此除了技术层面外还需要考虑逻辑思维的应用。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值