持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第11天,点击查看活动详情
题目链接:812. 最大三角形面积
题目描述
给定包含多个点的集合,从其中取三个点组成三角形,返回能组成的最大三角形的面积。
注意:
- 不存在重复的点。
- 结果误差值在 以内都认为是正确答案。
示例 1:
输入: points = [[0,0],[0,1],[1,0],[0,2],[2,0]]
输出: 2
解释:
这五个点如下图所示。组成的橙色三角形是最大的,面积为2。
整理题意
题目给出 n
个点的集合,要求我们取出三个点组成三角形,求三角形的最大面积为多少。
解题思路分析
首先观察题目数据范围,点集合的个数 n
在 [3, 50]
之间,由于数据范围较小,我们可以暴力解决该问题,我们枚举所有情况,分别计算每种情况下的三角形面积,取面积最大值即可。
问题转换到如何通过三个点计算由这三个点构成的三角形面积:
方法一:点到直线的距离
- 公式中的直线方程为:
- 为点 到直线 的距离
根据点到直线的距离公式,我们可以通过枚举的三个点中任意两个点求得直线方程中的系数
A
、B
和C
,让该直线作为三角形的底边;再由通过剩余一个点求得该点到直线的距离即为三角形的高,从而通过三角形面积公式(底乘高除二)求得三角形面积。
方法二:线性代数行列式
通过线性代数中所学的行列式我们可以直接通过三个点求得三角形的面积:
- 根据面积公式,带入三个点即可求得三角形面积。
具体实现
方法一:点到直线的距离
在实现方法一的过程中需要注意一些细节:
- 由于我们在求斜率时候会用到除法,需要保证除数不为零;
- 同时需要判断三点是否共线,三点共线无法构成三角形。
方法二:线性代数行列式
在方法二中同样也有一些细节需要注意:
- 公式中的分子可能得到负数,对于这种情况我们只需要取绝对值即可。
优化
在暴力枚举点的时候我们可以进行剪枝,因为对于三个点来说,暴力枚举时会遍历三次同种情况,我们可以通过遍历时直接跳过上一个节点遍历过的节点即可(具体可以看代码实现部分)。
由于题目要求结果误差值在 以内都认为是正确答案,所以我们需要采用
double
高精度来存储数据
复杂度分析
- 时间复杂度:,其中
n
是数组points
的长度。三重循环需要 。 - 空间复杂度:。
代码实现
方法一:点到直线的距离
class Solution {
private:
double S(int x0, int y0, int x1, int y1, int x2, int y2){
//注意两点横坐标相减不能为0
if(x1 == x2){
swap(x1, x0);
swap(y1, y0);
}
//如果三点一线,无法构成三角形
if(x1 == x2) return 0;
//斜率k
double k = (double)(y1 - y2) / (double)(x1 - x2);
double c = (double)y1 - k * x1;
//距离d
double d = abs(k * x0 - (double)y0 + (double)c);
d /= (double)sqrt(k * k + (double)1);
//底边长l
double l = (double)sqrt((double)(x1 - x2) * (x1 - x2) + (double)(y1 - y2) * (y1 - y2));
//面积s
double s = (double)l * d / (double)2;
return s;
}
public:
double largestTriangleArea(vector<vector<int>>& points) {
int n = points.size();
double ans = 0;
for(int i = 0; i < n; i++){
for(int j = i + 1; j < n; j++){
for(int k = j + 1; k < n; k++){
int x0 = points[i][0], y0 = points[i][1];
int x1 = points[j][0], y1 = points[j][1];
int x2 = points[k][0], y2 = points[k][1];
ans = max(ans, S(x0, y0, x1, y1, x2, y2));
}
}
}
return ans;
}
};
方法二:线性代数行列式
class Solution {
public:
double largestTriangleArea(vector<vector<int>>& points) {
int n = points.size();
double ans = 0;
for(int i = 0; i < n; i++){
for(int j = i + 1; j < n; j++){
for(int k = j + 1; k < n; k++){
int x1 = points[i][0], y1 = points[i][1];
int x2 = points[j][0], y2 = points[j][1];
int x3 = points[k][0], y3 = points[k][1];
double s = (double)abs(x1 * y2 + x2 * y3 + x3 * y1 - x1 * y3 - x2 * y1 - x3 * y2) / (double)2;
ans = max(ans, s);
}
}
}
return ans;
}
};
总结
- 因为该题数据范围较小,可以采用暴力枚举的方法以及运用数学公式进行解决,在代码实现部分有较多细节需要注意,遇到除法运算时需要考虑排除分母为零的情况,三点共线的特殊情况也需要注意,同时需要保证面积不为负。最后就是需要注意枚举时能够剪枝或者优化的地方,保证每种情况不重不漏。
- 可以看到方法一和方法二两种方法在时间复杂度上均为 ,都需要枚举所有情况:
- 我们还需考虑如果当数据范围增加时, 的时间复杂度无法通过时我们该如何解决。这里就需要使用到 凸包 算法来解决。凸包的时间复杂度为 ,同时保存凸包需要 的空间,也就是所谓的空间交换时间的做法。
结束语
如果生活将你置于低估,也一定给你留了上坡的路。与其抱怨,不如保持自律,积极用心,可能就会出现转机。只有不放弃,才会发现成就梦想的正是逆境中的亮剑、失败后的奋起。