593. 有效的正方形
593. 有效的正方形
给定2D空间中四个点的坐标 p1, p2, p3 和 p4,如果这四个点构成一个正方形,则返回 true 。
点的坐标 pi 表示为 [xi, yi] 。输入 不是 按任何顺序给出的。
一个 有效的正方形 有四条等边和四个等角(90度角)。
示例 1:
输入: p1 = [0,0], p2 = [1,1], p3 = [1,0], p4 = [0,1]
输出: True
示例 2:输入:p1 = [0,0], p2 = [1,1], p3 = [1,0], p4 = [0,12]
输出:false
示例 3:输入:p1 = [1,0], p2 = [-1,0], p3 = [0,1], p4 = [0,-1]
输出:true
提示:
p1.length == p2.length == p3.length == p4.length == 2
-104 <= xi, yi <= 104
一道很有意思的题目,看到这题,我最直观的想法是:
根据四个点生成两条线段,如果下面条件成立:
-
1、两条线段长度相等
-
2、两条线段垂直
-
3、两条线段中点是一样的
遍历所有可能性(其实只有三种),只要有一种成立,就可以组成正方形。
ps:中间有几个坑,我写的时候全犯了:
-
1、垂直不能用斜率计算,因为平行于y轴的线段斜率无穷大,用坐标计算会出现 『除 0 』,因此用 向量点积 x1 * x2 + y1 * y2 = 0
-
2、两条线段垂直,长度相等,还需要判断中点重合
-
2、判断题目到底给了几个不同的点,用set去重。
代码如下:
class Solution: def validSquare(self, p1, p2, p3, p4): if len(set([tuple(p1),tuple(p2),tuple(p3),tuple(p4)])) < 4: return False if self.cacl_dis(p1, p2) == self.cacl_dis(p3, p4) and self.is_square(p1, p2, p3, p4) and self.cacl_mid(p1, p2, p3, p4): return True if self.cacl_dis(p1, p3) == self.cacl_dis(p2, p4) and self.is_square(p1, p3, p2, p4) and self.cacl_mid(p1, p3, p2, p4): return True if self.cacl_dis(p1, p4) == self.cacl_dis(p2, p3) and self.is_square(p1, p4, p2, p3) and self.cacl_mid(p1, p4, p2, p3): return True return False def cacl_mid(self, p1, p2, p3, p4): x1, y1 = (p2[0] + p1[0])/2, (p2[1] + p1[1])/2 x2, y2 = (p3[0] + p4[0])/2, (p3[1] + p4[1])/2 return x1 == x2 and y1 == y2 def cacl_dis(self, p1, p2): return sqrt((p2[1] - p1[1]) ** 2 + (p2[0] - p1[0]) ** 2) def is_square(self, p1, p2, p3, p4): x1, y1 = p2[0] - p1[0], p2[1] - p1[1] x2, y2 = p4[0] - p3[0], p4[1] - p3[1] return x1*x2 + y1*y2 == 0
然后就在评论区看到了很多有意思的解法:
方法一:
分享一种不用计算边长,也不用考虑点的顺序的方法:
如果以正方形中心点为坐标原点,则正方形的任意顶点绕原点旋转 90° 后仍然在这四个点中。 因此只需要检查每个点旋转 90° 后,是否还存在于四个点组成的集合中即可。
而二维平面旋转 90° 很容易表示: (x, y)
逆时针旋转 90° 后为 (-y, x)
#define Cen2Ori(p) {p[0]*4-cenX, p[1]*4-cenY} class Solution { public: bool validSquare(vector<int>& p1, vector<int>& p2, vector<int>& p3, vector<int>& p4) { // 中心点,缩放 4 倍,避免除法 int cenX = p1[0] + p2[0] + p3[0] + p4[0]; int cenY = p1[1] + p2[1] + p3[1] + p4[1]; // 将中心点移动到坐标原点 vector<array<int, 2>> pts = {Cen2Ori(p1), Cen2Ori(p2), Cen2Ori(p3), Cen2Ori(p4)}; // 将四个顶点存入哈希表 auto arrayHash = [fn = hash<long>{}] (const array<int, 2>& arr) -> size_t { // 哈希函数 return fn(*((long const*)arr.data())); }; unordered_set<array<int, 2>, decltype(arrayHash)> pts_set(pts.begin(), pts.end(), 0, arrayHash); if(pts_set.size() < 4) return false; // 检查每个点旋转 90 度以后的点是否在哈希表中 for(auto &pt: pts){ if(!pts_set.count({-pt[1], pt[0]})) return false; } return true; } };
方法二:
每两个点算一下距离,要么是边要么是对角线,如果出现0肯定不是,如果是正方形只有两种距离的结果。
class Solution { public boolean validSquare(int[] p1, int[] p2, int[] p3, int[] p4) { Set<Integer> set = new HashSet<Integer>(); set.add(distanceSquare(p1, p2)); set.add(distanceSquare(p1, p3)); set.add(distanceSquare(p1, p4)); set.add(distanceSquare(p2, p3)); set.add(distanceSquare(p2, p4)); set.add(distanceSquare(p3, p4)); return set.size() == 2 && !set.contains(0); } private static int distanceSquare(int[] a, int[] b) { int i = a[0] - b[0]; int j = a[1] - b[1]; return i * i + j * j; } }
可能有点问题:
1、如果出现一个正三角形 然后最后一个点在正三角形中间会如何呢 依旧满足 边长相等 且数值为2 但不是正方形
2、两个等边三角形拼起来符合你这个,但不是正方形
方法三:
任选三个点都是直角三角形
ublic class Solution { public boolean validSquare(int[] p1, int[] p2, int[] p3, int[] p4) { //任选三个点 都是 一个直角三角形 return isRightTriangle(p1, p2, p3) && isRightTriangle(p1, p2, p4) && isRightTriangle(p1, p3, p4) && isRightTriangle(p2, p3, p4); } public boolean isRightTriangle(int[] p1, int[]p2, int[] p3){ int d1 = (p1[0] - p2[0]) * (p1[0] - p2[0]) + (p1[1] - p2[1]) * (p1[1] - p2[1]); int d2 = (p2[0] - p3[0]) * (p2[0] - p3[0]) + (p2[1] - p3[1]) * (p2[1] - p3[1]); int d3 = (p3[0] - p1[0]) * (p3[0] - p1[0]) + (p3[1] - p1[1]) * (p3[1] - p1[1]); if(d1 > d2 && d2 == d3 && d2 + d3 == d1 || d2 > d1 && d1 == d3 && d1 + d3 == d2 || d3 > d1 && d1 == d2 && d1 + d2 == d3){ return true; } return false; } }