1. 程式人生 > >LeetCode--max-points-on-a-line

LeetCode--max-points-on-a-line

Given n points on a 2D plane, find the maximum number of points that lie on the same straight line.

給定二維平面上的n個點,找出位於同一直線上的最大點數。

雜湊表法

複雜度

O(N^2) 時間 O(N) 空間, N為點數

思路

應知應會:

  1. 平面裡確定一條直線要兩個資料,可以是兩個不同的點(高中數學做法),也可以是一個點加一個斜率(這道題做法)

  2. 斜率k = (y2 - y1)/(x2 - x1),當 x1 == x2 時,分母為0,斜率為無窮,表示和y軸平行的直線們

  3. 在計算機裡使用double表示斜率,是不嚴謹的也是不正確的,double有精度誤差,double是有限的,斜率是無限的,無法使用有限的double表示無限的斜率,不過此題的test case沒有涉及這個問題

  4. 表示斜率最靠譜的方式是用最簡分數,即分子分母都無法再約分了。分子分母同時除以他們的最大公約數gcd即可得到最簡分數

  5. gcd(a,b),一般求的是兩個正整數的gcd。這道題a和b有可能是0,分別表示與x軸或y軸平行的斜率(注意ab不能同時為0,表示同一個點沒有意義),所以這道題我們規定ab取值範圍:a>=0,b>=0。至於負數,先變成正數取gcd,再確定最終斜率的正負

  6. gcd ( a , b ) = (b == 0) ? a : gcd ( b , a % b ), a,b是任意非負整數且a,b至少有一個不為0

  7. 觀察gcd(a,b),假設a,b為非負整數:

    1. a和b中有一個為零,那麼gcd為另一個不為0的數;

    2. a和b都為0,gcd為0;

演算法:

沒什麼演算法就是窮舉:
對每個點,都計算一下該點和其他點連線的斜率,這樣對於這個點來說,相同斜率的直線有多少條,就意味著有多少個點在同一條直線上,因為這些直線是相同的。另外,如果計算過點A和點B的直線,當算到點B時,就不用再和A連線了,因為AB這條直線上的點數已經都計算過了。這裡,我們用雜湊表,以斜率為key,記錄有多少重複直線。

注意

  • 求gcd的utility:

public int gcd(int a, int b) {
    if (b == 0)
        return a;
    else
        return gcd(b, a % b);
}
  • 兩層迴圈外層 i 從 0 到 n, 內層 j 從 i+1 到 n

  • 如果想要自定義Slope(斜率)類作為HashMap的Key的話要重寫hashCode()和equals()函式, 偷懶的話可以把斜率的分數表示成String作為Key,這樣就省了一些事

  • hashmap的value的含義應定義為:過cur點以key為斜率的直線有幾個除了cur以外的點。在算完 過cur的所有直線後,統計cur點的總個數howManyCur,加到當前點最多的直線上,即可得到含cur點的最大直線上的點數。

  • 程式碼:

  • import java.util.*;
    /**
     * Definition for a point.
     * class Point {
     *     int x;
     *     int y;
     *     Point() { x = 0; y = 0; }
     *     Point(int a, int b) { x = a; y = b; }
     * }
     */
    public class Solution {
        public int maxPoints(Point[] points) {
            if (points.length <= 1)
                return points.length;
            int maxUniv = Integer.MIN_VALUE;
            for (int i = 0; i < points.length; i++) {
                Point cur = points[i];
                HashMap<String, Integer> map = new HashMap<String, Integer>();
                int howManyCur = 1, maxLocal = 0;
                for (int j = i + 1; j < points.length; j++) {   //這裡可以從i+1開始,之前的都算過了
                        Point iter = points[j];
                        if (iter.x == cur.x && iter.y == cur.y) {//同一頂點
                            howManyCur += 1;
                        } 
                        else {          //不同頂點
                            String key = getSlopeInString(cur, iter);
                            //map裡存(過cur點,斜率key)代表的直線有多少除了cur的點
                            map.put(key, map.containsKey(key) ? map.get(key) + 1 : 1);
                            maxLocal = Math.max(maxLocal, map.get(key));
                        }
                }
                maxLocal = howManyCur + maxLocal;
                maxUniv = Math.max(maxLocal, maxUniv);
            }
            return maxUniv;
        }
        public String getSlopeInString(Point cur, Point iter) {
            int numerator = iter.y - cur.y;
            int denominator = iter.x - cur.x;
            String sign = getSign(numerator, denominator);
            int gcd = gcd(Math.abs(numerator), Math.abs(denominator));//0和任意一個非零數'a'的gcd為'a',0和0的gcd為0,所以斜率為無窮的情況分母為0
            return sign + Math.abs(numerator)/gcd + "/" + Math.abs(denominator)/gcd;
        }
        //a和b為非負整數 且 a和b不同時為0
        public int gcd(int a, int b) {
            if (b == 0)
                return a;
            else
                return gcd(b, a % b);
        }
        public String getSign(int a, int b) {
            if (a <= 0 && b <= 0 || a >= 0 && b >= 0)
                return "+";
            else 
                return "-";
        }
    }