演算法之陣列——共直線的最多點數

it_was發表於2020-10-08

直線上最多的點數

難度困難:smirk:

給定一個二維平面,平面上有 n 個點,求最多有多少個點在同一條直線上。

示例 1:
輸入: [[1,1],[2,2],[3,3]]
輸出: 3
解釋:
演算法之陣列——共直線的最多點數

示例 2:
輸入: [[1,1],[3,2],[5,3],[4,1],[2,3],[1,4]]
輸出: 4
解釋:
演算法之陣列——共直線的最多點數

思路

首先需要確定兩個點在一條直線上 —— 即可以透過斜率的來表示,如果一個點p1和另外兩個點p2,p3都在一條直線上,那麼必有 k(p1,p2) = k(p1,p3);
所以可以透過斜率來確定與該點在同一條直線上的個數 ,如下圖

演算法之陣列——共直線的最多點數

p(0,0),與其餘5個點形成的斜率分別為

(0,0) - (1,5) -> k = 5
(0,0) - (1,4) -> k = 4
(0,0) - (3,3) - (4,4) -> k = 1
(0,0) - (5,1) -> k = 0.2

透過上圖可以發現共直線點數最多的是 以當前目標點 p(0,0),斜率為 1 的直線,其上加上本身共有三個點!所以我們可以對每個點維護一個記錄斜率的雜湊表!每次獲取當前斜率下的點數並判斷更新!最終獲得該點形成所有斜率的直線中,點數最多的一條!:boom: :boom: :boom:

需要注意以下幾點

  • 對於重複點的處理,例如目標點(0,0) 和 待比較點 (0,0)兩個點,這時(0,0)可以與自身形成任意斜率的點,所以這個重複數量需要單獨處理
  • 對於在同一豎線上的點集是無法計算他的斜率的,故應該單獨用一個標記記錄,比如 “inf”
  • 如何計算斜率才能更好地保證唯一性? 可以用dxdy 的最大公約數,將分子分母約分,這裡並沒有真正計算出其除法運算的結果,是因為為了避免除不盡而出現小數的不精確性!!!例如 : 3/9 和 3/7 這種,double型別的浮點數判斷相等是有誤差的,所以這我們就用斜率 的 最簡形式(不能約分)的字串來代表此唯一性的斜率,即 dx / gcd(dx,dy) + "/" + dy / gcd(dx,dy)

透過程式碼體會:

package ddx.December.day8;

import java.util.HashMap;
import java.util.HashSet;

//給定一個二維平面,平面上有 n 個點,求最多有多少個點在同一條直線上。
public class Normal_149 {
    public static void main(String[] args) {
        int[][] points = new int[][]{
               // {1, 1}, {3, 2}, {5, 3}, {4, 1}, {2, 3}, {1, 4}
                //{1, 1}, {1, 2}, {1, 3}, {1, 4}, {1, 5}, {1, 6}
                {0, 0}, {1, 1}, {1,-1}
        };
        maxPoints(points);
    }


    public static int maxPoints(int[][] points) {
        if (points == null || points.length == 0 || points[0] == null || points[0].length == 0) {
            return 0;
        }

        int len = points.length;
        int max = 1;
        for (int i = 0; i < len; i++) {
            int x1 = points[i][0];
            int y1 = points[i][1];
            int repeat = 0;
            int cur_max = 0;
            HashMap<String, Integer> hashMap = new HashMap<>();//透過維護一個唯一性的斜率雜湊!
            for (int j = i + 1 ; j < len; j++) {//從 i + 1開始,不需要再重複比較!
                //先拿到當前基準點的橫縱座標
                int x2 = points[j][0]; 
                int y2 = points[j][1];
                if (x1 == x2 && y1 == y2) {
                    //重複點可以為任何斜率!!!所以需要單獨記錄
                    repeat++;
                    continue;
                }
                if (x1 == x2) { //直線豎直!!!需要用特殊標記來記錄 - "inf"
                    if (!hashMap.containsKey("inf")) {
                        hashMap.put("inf", 1);
                    } else {
                        hashMap.put("inf", hashMap.get("inf") + 1);
                    }
                    cur_max = Math.max(hashMap.get("inf"), cur_max); //因為和當前點處於豎線的點是無法計算斜率,所以需要提前比較當前直線上的點數
                    continue;
                }
                //準備計算斜率
                int dx = x2 - x1; 
                int dy = y2 - y1;
                int _gcd = gcd(dx, dy); //計算分子分母的最大公約數
                String k = (dy / _gcd) + "/" + (dx / _gcd); //使用最大公約數約分一下,保證斜率的唯一性,並用字串記錄,避免double型別的不精確
                if (!hashMap.containsKey(k)) {
                    hashMap.put(k, 1);
                } else {
                    hashMap.put(k, hashMap.get(k) + 1);
                }
                cur_max = Math.max(hashMap.get(k), cur_max); //不斷更新以point[i]為目標點的最大值
            }
            max = Math.max(cur_max + repeat + 1, max); //每一輪下來都得更新 
            System.out.println(hashMap.toString());
        }

        System.out.println(max);
        return max;
    }

    public static int gcd(int x, int y) {
        if (y == 0) {
            return x;
        } else {
            return gcd(y, x % y);
        }
    }
}
本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章