[数学:三点共线] 1037. 有效的回旋镖

212 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第10天,点击查看活动详情

每日刷题 2022.06.08

题目

  • 给定一个数组 points ,其中 points[i] = [xi, yi] 表示 X-Y 平面上的一个点,如果这些点构成一个 回旋镖 则返回 true 。
  • 回旋镖 定义为一组三个点,这些点 各不相同 且 不在一条直线上 。

示例

  • 示例1
输入: points = [[1,1],[2,3],[3,2]]
输出: true
  • 示例2
输入: points = [[1,1],[2,2],[3,3]]
输出: false

提示

  • points.length == 3
  • points[i].length == 2
  • 0 <= xi, yi <= 100

相似题目

  • 2280. 表示一个折线图的最少线段数
  • 这个题目的坑点:1.对于所有的节点需要进行排序,因为节点无序的话,就不能判断是否是连续的,可能会额外的计算线段数。2.求斜率的时候,存在精度的问题,因为数值比较大,精度会存在丢失的情况,那么就会导致两个斜率不同的判断成相同的。

解题思路

解决方法1

  • 可以将其转换成三点一线的做法,判断相邻的三个点是否在一条直线上,如果在一条直线上,就不需要新增线段数;反之就需要新增额外的线段数。(使用向量叉积的做法)

解决方法2

  • 如果乘积得到的数据也很大的话,会超出的,那么就可以使用GCD的做法。计算斜率时以元组的形式保留分子和分母的最简式。通过分别计算上一次以及这一次的xy的最大公约数GCD,将x和y化为最简式后,进行比较是否相等即可,这样就可以避免精度的问题。
var minimumLines = function(stockPrices) {
  let s = stockPrices, n = s.length, sum = 0;
  if(n == 1) return sum;
  function gcd(x, y) {
    return y == 0 ? x : gcd(y, x % y);;
  }
  s.sort((a, b) => a[0] - b[0]);
  sum++;
  for(let i = 1; i < n - 1; i++) {
    let x = s[i][0] - s[i - 1][0], y = s[i][1] - s[i - 1][1];
    // 计算上一个x和y的最大公约数
    let k = gcd(x, y);
    x /= k, y /= k;
    let x1 = s[i + 1][0] - s[i][0], y1 = s[i + 1][1] - s[i][1];
    // 计算当前的x和y差值的最大公约数
    let k1 = gcd(x1, y1);
    x1 /= k1, y1 /= k1;
    // 比较两个化简后的分子、分母是否相等
    if(x == x1 && y == y1) continue;
    // 只有斜率不相等的时候,才需要++
    else sum++;
  }
  return sum;
};

本题的解题思路

  • 分析:需要三个点个不相同,并且不在一条直线上
  • 因此可以得到两个要求:各不相同不在一条直线上
  • 既然是要求线段数,那么我们思考下,一共有n个数,那么就有n - 1个线段

斜率做法

  • 计算两个点之间的线段的斜率,比较这次的斜率和上次的是否一致
  • 注意⚠️,除法的精度丢失和乘法的数据超出,必要的时候可以使用GCD来解决。
假设坐标:a(x1, y1) ,b(x2, y2) ,c(x3, y3)
(y1 - y2) / (x1 - x2) = (y2 - y3) / (x2 - x3) = k
转换成乘法:(y1 - y2) * (x2 - x3) = (x1 - x2) * (y2 - y3)
  • 最开始的时候,记上一轮的preX = 0, preY = 1, k = 1 / 0,因为对于每一个节点来说,其比较的过程中记录的是其相连的后面的那条边,那么对于第一个点来说,必须要res++(线段数+1)

向量做法

  • 因为有三个点,a,b,c
  • 几何原理:证明三点不共线,只需要从同一点出发到两个不同点的斜率不同即可。
  • 如果三个点共线的话,则向量ab与ac是平行的,因此(向量叉乘做法):
    假设坐标:a(x1, y1) ,b(x2, y2) ,c(x3, y3)
    向量ab = ((x2 - x1), (y2 - y1))
    向量ac = ((x3 - x1), (y3 - y1))
    如果两直线平行,则:
    (y2 - y1) / (x2 - x1) = (y3 - y1) / (x3 - x1)
    因为除法会出现0的问题和精度问题,
    因此转换成乘法计算,
    即:(y2 - y1) * (x3 - x1) = (y3 - y1) * (x2 - x1)
    
  • 因为向量做法,最终会停留在i = n - 2的位置上,跳出循环,那么此时还存在一条边没有判断。假设跳出的时候,三点是在一条直线上的,那么最后的这条边需要记录;反之,如果跳出的时候,三点不是在一条直线上的,那么最后这个是需要记录的
  • 因此不论三点在不在一条线上最后线段都需要+1,也可以反向思维,初始化线段数为n - 1,这样只需要碰到在一条直线上的减去一个线段数即可。

面积做法

  • 三点如果共线的话,那么组成的三角形面积为0;反之,面积不为0
  • 已知三个点的坐标,求解三角形的面积公式如下:需要牢记)
S = 1 / 2 * [(x1y2 - x2y1)+ (x2y3 - x3y2) + (x3y1 - x1y3)]

AC代码

/**
 * @param {number[][]} points
 * @return {boolean}
 */
var isBoomerang = function(points) {
  // 计算三个点之间的斜率
  const p = points;
  return (p[1][1] - p[0][1]) * (p[2][0] - p[1][0]) != (p[2][1] - p[1][1]) * (p[1][0] - p[0][0]);
};