面試題 16.03. 交點 - 計算幾何

面試題 16.03. 交點


給定兩條線段(表示爲起點start = {X1, Y1}和終點end = {X2, Y2}),如果它們有交點,請計算其交點,沒有交點則返回空值。
要求浮點型誤差不超過10^-6。若有多個交點(線段重疊)則返回 X 值最小的點,X 座標相同則返回 Y 值最小的點。

輸入:
line1 = {0, 0}, {1, 0}
line2 = {1, 1}, {0, -1}
輸出: {0.5, 0}

來源:https://leetcode-cn.com/problems/intersection-lcci/
題解:

  1. 排除界限不重合的情況
if (Math.max(a[0], b[0]) < Math.min(c[0], d[0]) ||
    Math.max(c[0], d[0]) < Math.min(a[0], b[0]) ||
    Math.max(a[1], b[1]) < Math.min(c[1], d[1]) ||
    Math.max(c[1], d[1]) < Math.min(a[1], b[1])) {
    return [];
}
  1. 根據叉乘的正負判斷兩個點是否在一條線段的兩側,原理見下圖
    相交
    不相交
/* 獲取向量叉乘值 sx x sy */
function getCross(s, x, y) {
    let x1, y1, x2, y2;
    [x1, y1] = [x[0] - s[0], x[1] - s[1]];
    [x2, y2] = [y[0] - s[0], y[1] - s[1]];
    return x1 * y2 - x2 * y1;
}
let acb = getCross(a, c, b);//ac x ab
let adb = getCross(a, d, b);//ad x ab
let cad = getCross(c, a, d);//ca x cd
let cbd = getCross(c, b, d);//cb x cd
  1. 確保有交點後根據y=kx+b根據數學公式獲取兩條線段的k和b,提前排序是爲了垂直時後面可以直接取到最小值
/* 統一端點順序 */
if (a[0] > b[0] || a[0] == b[0] && a[1] > b[1]) [a, b] = [b, a];
if (c[0] > d[0] || c[0] == d[0] && c[1] > d[1]) [c, d] = [d, c];
/* k = (y2 - y1) / (x2 - x1) */
let k1 = (b[1] - a[1]) / (b[0] - a[0]);
let k2 = (d[1] - c[1]) / (d[0] - c[0]);
/* b = (x2 * y1 - x1 * y2) / (x2 - x1) */
let b1 = (b[0] * a[1] - a[0] * b[1]) / (b[0] - a[0]);
let b2 = (d[0] * c[1] - c[0] * d[1]) / (d[0] - c[0]);
  1. 對於共線情況,遍歷4個點,先判斷是否是公共點,再根據題目要求選x最小的或x相等y最小的
/* 判斷斜率相同情況下x,y是否在兩條線段上 */
function isInLine(x, y) {
    for (let [s, e] of [[a, b], [c, d]]) {
        /* 求邊界值 */
        let [maxx, maxy] = [Math.max(s[0], e[0]), Math.max(s[1], e[1])];
        let [minx, miny] = [Math.min(s[0], e[0]), Math.min(s[1], e[1])];
        if (x < minx || x > maxx || y < miny || y > maxy)
            return false;
    }
    return true;
}
/* 共線 包括垂直的情況 */
if (k1 == k2) {
    let x = null, y = null;
    for (let [xx, yy] of [a, b, c, d]) {
        if (isInLine(xx, yy)) {
            if (x == null && y == null ||
                xx < x || xx == x && yy < y) {
                x = xx; y = yy;
            }
        }
    }
    return [x, y];
}
  1. 若只有一條線垂直,直接取垂直線的橫座標,計算y值,若沒有垂直的線,帶公式求x,y即可
else if (k1 === Infinity) {//ab垂直
    return [a[0], k2 * a[0] + b2];
}
else if (k2 === Infinity) {//cd垂直
    return [c[0], k1 * c[0] + b1];
}
else {//正常相交情況
    let x = (b2 - b1) / (k1 - k2);
    return [x, x * k1 + b1];
}
  1. 完整代碼
var intersection = function (a, b, c, d) {
    /* 排除區間不相交的情況 */
    if (Math.max(a[0], b[0]) < Math.min(c[0], d[0]) ||
        Math.max(c[0], d[0]) < Math.min(a[0], b[0]) ||
        Math.max(a[1], b[1]) < Math.min(c[1], d[1]) ||
        Math.max(c[1], d[1]) < Math.min(a[1], b[1])) {
        return [];
    }
    /* 獲取向量叉乘值 sx x sy */
    function getCross(s, x, y) {
        let x1, y1, x2, y2;
        [x1, y1] = [x[0] - s[0], x[1] - s[1]];
        [x2, y2] = [y[0] - s[0], y[1] - s[1]];
        return x1 * y2 - x2 * y1;
    }
    let acb = getCross(a, c, b);//ac x ab
    let adb = getCross(a, d, b);//ad x ab
    let cad = getCross(c, a, d);//ca x cd
    let cbd = getCross(c, b, d);//cb x cd
    /*有交點 b,c在ab兩側 ab在cd兩側*/
    if (acb * adb <= 0 && cad * cbd <= 0) {
        /* 統一端點順序 */
        if (a[0] > b[0] || a[0] == b[0] && a[1] > b[1]) [a, b] = [b, a];
        if (c[0] > d[0] || c[0] == d[0] && c[1] > d[1]) [c, d] = [d, c];
        /* k = (y2 - y1) / (x2 - x1) */
        let k1 = (b[1] - a[1]) / (b[0] - a[0]);
        let k2 = (d[1] - c[1]) / (d[0] - c[0]);
        /* b = (x2 * y1 - x1 * y2) / (x2 - x1) */
        let b1 = (b[0] * a[1] - a[0] * b[1]) / (b[0] - a[0]);
        let b2 = (d[0] * c[1] - c[0] * d[1]) / (d[0] - c[0]);
        /* 判斷斜率相同情況下x,y是否在兩條線段上 */
        function isInLine(x, y) {
            for (let [s, e] of [[a, b], [c, d]]) {
                /* 求邊界值 */
                let [maxx, maxy] = [Math.max(s[0], e[0]), Math.max(s[1], e[1])];
                let [minx, miny] = [Math.min(s[0], e[0]), Math.min(s[1], e[1])];
                if (x < minx || x > maxx || y < miny || y > maxy)
                    return false;
            }
            return true;
        }
        /* 共線 包括垂直的情況 */
        if (k1 == k2) {
            let x = null, y = null;
            for (let [xx, yy] of [a, b, c, d]) {
                if (isInLine(xx, yy)) {
                    if (x == null && y == null ||
                        xx < x || xx == x && yy < y) {
                        x = xx; y = yy;
                    }
                }
            }
            return [x, y];
        }
        else if (k1 === Infinity) {//ab垂直
            return [a[0], k2 * a[0] + b2];
        }
        else if (k2 === Infinity) {//cd垂直
            return [c[0], k1 * c[0] + b1];
        }
        else {//正常相交情況
            let x = (b2 - b1) / (k1 - k2);
            return [x, x * k1 + b1];
        }
    }
    /* 沒有交點 */
    return [];
};
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章