这道LeetCode题目要求我们在一组给定的二维平面点中,找出能构成矩形的最小面积。与基础版本不同的是,这里的矩形可以是任意旋转角度的(即边不一定平行于坐标轴)。这大大增加了问题的复杂度,因为我们需要考虑更普遍的几何关系。
关键约束条件:
几何学原理应用:
判断四个点能否构成矩形,我们需要利用以下几何性质:
在实际编码中,我们会将这些几何条件转化为数值计算。由于浮点数精度问题,我们需要设置合理的误差范围(根据题目要求是10^-5)。
最直观的方法是四重循环枚举所有可能的四个点组合,然后检查是否满足矩形条件。但这样的时间复杂度是O(n^4),对于n=50时达到6百万级别,虽然勉强可以通过,但我们需要更聪明的做法。
优化思路:
这样可以将复杂度降到O(n^3),对于n=50是125,000次操作,完全可行。
更优雅的解法是利用矩形的对角线性质:
这种方法时间复杂度为O(n^2),需要约2500次操作,是最优解。
由于涉及大量浮点运算,我们需要特别注意:
c复制#define EPSILON 1e-7
int double_equal(double a, double b) {
return fabs(a - b) < EPSILON;
}
对于几何计算,我们封装了向量操作:
c复制typedef struct {
double x;
double y;
} Vector;
Vector make_vector(int* p1, int* p2) {
Vector v;
v.x = p2[0] - p1[0];
v.y = p2[1] - p1[1];
return v;
}
double dot_product(Vector v1, Vector v2) {
return v1.x * v2.x + v1.y * v2.y;
}
C语言中没有内置哈希表,我们需要自己实现:
c复制typedef struct {
int x;
int y;
} Point;
typedef struct {
Point key;
UT_hash_handle hh;
} HashItem;
HashItem* hashFind(HashItem** obj, int x, int y) {
Point temp = {x, y};
HashItem* pEntry = NULL;
HASH_FIND(hh, *obj, &temp, sizeof(Point), pEntry);
return pEntry;
}
void hashInsert(HashItem** obj, int x, int y) {
HashItem* pEntry = hashFind(obj, x, y);
if (pEntry == NULL) {
pEntry = malloc(sizeof(HashItem));
pEntry->key.x = x;
pEntry->key.y = y;
HASH_ADD(hh, *obj, key, sizeof(Point), pEntry);
}
}
c复制#include <math.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#define EPSILON 1e-7
typedef struct {
int x;
int y;
} Point;
typedef struct {
Point key;
UT_hash_handle hh;
} HashItem;
HashItem* hashFind(HashItem** obj, int x, int y) {
Point temp = {x, y};
HashItem* pEntry = NULL;
HASH_FIND(hh, *obj, &temp, sizeof(Point), pEntry);
return pEntry;
}
void hashInsert(HashItem** obj, int x, int y) {
HashItem* pEntry = hashFind(obj, x, y);
if (pEntry == NULL) {
pEntry = malloc(sizeof(HashItem));
pEntry->key.x = x;
pEntry->key.y = y;
HASH_ADD(hh, *obj, key, sizeof(Point), pEntry);
}
}
double minAreaFreeRect(int** points, int pointsSize, int* pointsColSize) {
HashItem* pointSet = NULL;
for (int i = 0; i < pointsSize; i++) {
hashInsert(&pointSet, points[i][0], points[i][1]);
}
double minArea = DBL_MAX;
for (int i = 0; i < pointsSize; i++) {
for (int j = 0; j < pointsSize; j++) {
if (i == j) continue;
for (int k = 0; k < pointsSize; k++) {
if (i == k || j == k) continue;
int* p1 = points[i];
int* p2 = points[j];
int* p3 = points[k];
// 检查向量p2p1和p2p3是否垂直
int dx1 = p1[0] - p2[0];
int dy1 = p1[1] - p2[1];
int dx2 = p3[0] - p2[0];
int dy2 = p3[1] - p2[1];
if (dx1 * dx2 + dy1 * dy2 != 0) continue;
// 计算第四个点
int x4 = p1[0] + p3[0] - p2[0];
int y4 = p1[1] + p3[1] - p2[1];
if (hashFind(&pointSet, x4, y4)) {
double area = sqrt(dx1*dx1 + dy1*dy1) * sqrt(dx2*dx2 + dy2*dy2);
if (area < minArea) {
minArea = area;
}
}
}
}
}
HASH_CLEAR(hh, pointSet);
return minArea == DBL_MAX ? 0.0 : minArea;
}
我们实现的算法有三重循环:
总时间复杂度为O(n^3),空间复杂度为O(n)用于存储点集。
中点-距离哈希法:可以优化到O(n^2)
早期剪枝:
并行计算:
在实际编码中,我们需要特别注意以下边界情况:
处理技巧:
c复制// 在比较浮点数时使用相对误差
int double_equal(double a, double b) {
return fabs(a - b) < EPSILON * fmax(fabs(a), fabs(b));
}
// 检查三点是否共线
int isCollinear(int* p1, int* p2, int* p3) {
return (p2[1] - p1[1]) * (p3[0] - p2[0]) ==
(p3[1] - p2[1]) * (p2[0] - p1[0]);
}
全面的测试应该包括:
c复制// 输入:[[1,2],[2,1],[1,0],[0,1]]
// 输出:2.0
c复制// 输入:[[0,1],[1,0],[2,1]]
// 输出:0.0
c复制// 最小输入:[[0,0],[0,1],[1,0],[1,1]]
// 最大输入:50个随机点
c复制// 接近误差边界的矩形
在实现这个算法时,我总结了以下经验教训:
浮点数比较:直接使用==比较浮点数会导致错误,必须使用误差范围比较。
哈希表使用:UT_hash_handle需要正确初始化和清理,否则会导致内存泄漏。
几何公式验证:在纸上先验证几何公式的正确性,避免实现错误。
循环优化:内层循环可以从i+1开始,避免重复计算,但要注意索引关系。
内存管理:C语言需要手动释放哈希表内存,这在其他语言中容易被忽视。
提示:在LeetCode环境中使用UT_hash.h需要添加声明,这在竞赛编程中不常见,需要特别注意。
这个问题可以延伸到更高维度或更复杂的几何形状:
对于实际应用,如图像处理中的矩形检测,我们还需要考虑:
这个题目很好地结合了几何知识与算法设计,是训练计算几何思维的良好练习。理解如何将几何性质转化为可计算的数值条件,是解决此类问题的关键。