JAVA 判斷兩個單鏈表是否相交并求交點

在上一篇文檔中,通過java實現了單鏈表反轉的問題,之后發現一個更有意思的問題就是如何判斷兩個鏈表是否相交?如果相交,則需要得到交點。
對于這個問題,需要分別考慮鏈表上是否存在環的情況。

//鏈表節點
public class DataNode {

    private int data;
    private DataNode next;

    public int getData() {
        return data;
    }

    public void setData(int data) {
        this.data = data;
    }

    public DataNode getNext() {
        return next;
    }

    public void setNext(DataNode next) {
        this.next = next;
    }

    public DataNode(int data) {
        this.data = data;
    }
    
}

1.兩個鏈表都不存在環

對于這種情況,如果兩個鏈表相交,又都不存在環,那么不難想象這兩個鏈表共同構成了一個Y型。因此,只要分別遍歷這兩個鏈表,找到尾端節點,判斷尾端節點是否相同即可確認是否相交。
如果要求這種情況的交點,由于相交部分全部都相同,因此,只需要先得到兩個鏈表的差,用兩個指針分別指向這兩個鏈表P1,P2假定P1與P2相差為N,那么將P1移動N個節點后,P1與P2同時出發,第一個相等的節點即為交點。

/**
     * 無環情況下,判斷兩個鏈表是否相交,只需要遍歷鏈表,判斷尾節點是否相等即可。
     * @param h1
     * @param h2
     * @return
     */
    public static boolean isJoinNoLoop(DataNode h1,DataNode h2) {
        DataNode p1 = h1;
        DataNode p2 = h2;
        while(null != p1.getNext())
            p1 = p1.getNext();
        while(null != p2.getNext())
            p2 = p2.getNext();
        return p1 == p2;
    }
    
    /**
     * 無環情況下找到第一個相交點
     * 方法: 算出兩個鏈表的長度差為x,長鏈表先移動x步,之后兩鏈表同時移動,直到相遇的第一個交點。
     * @param h1
     * @param h2
     * @return
     */
    public static DataNode getFirstJoinNode(DataNode h1,DataNode h2) {
        int length1 = 0;
        int length2 = 0;
        while(null != h1.getNext()) {
            length1 ++;
            h1 = h1.getNext();
        }
        while(null != h2.getNext()) {
            length1 ++;
            h2 = h2.getNext();
        }
        return length1>=length2?getNode(h1,length1,h2,length2):getNode(h2,length2,h1,length1);
    }

這是最樂觀的一種情況,但是還需要考慮鏈表上存在環的情況。那么還需要添加一個方法,判斷鏈表上是否存在環。
對于如何判斷鏈表上是否存在環,解決辦法是采用快慢指針,兩個指針P1、P2分別指向同一個鏈表的頭節點,之后,P1一次前進兩個節點,P2一次前進一個節點。如果最終P1和P2能重合,則說明一定存在交點。反之如果最終P1或者P2存在一個為空的情況,則說明這兩個鏈表不相交。

/**
     * 判斷是否存在環  
     * 步驟:設置兩個指針同時指向head,其中一個一次前進一個節點(P1),另外一個一次前進兩個節點(P2)。
     * p1和p2同時走,如果其中一個遇到null,則說明沒有環,如果走了N步之后,二者指向地址相同,那么說明鏈表存在環。
     * @param h
     * @return
     */
    public static boolean isLoop(DataNode h) {
        DataNode p1 = h;
        DataNode p2 = h;
        while(p2.getNext() != null && p2.getNext().getNext()!=null){
            p1 = p1.getNext();
            p2 = p2.getNext().getNext();
            if(p1 == p2) 
                break;
        }
        return !(p1==null||p2==null);
    }

因此上述問題還有另外一個解法,將其中一個鏈表首尾相連,檢測另外一個鏈表是否存在環,如果存在(該環就是首尾相連的鏈表),則兩個鏈表相交,而檢測出來的依賴環入口即為相交的第一個點。需找出環的入口,設置p1,p2兩個指針,同樣一個走一步一個走兩步,兩者相遇則必在環上某一點相遇,記下此位置p1=p2,在p1和p2重合后,設置一個p3指向表頭,然后p1和p3每次同時行走一步,每步前進一個節點,等到p1和p3重合時,重合的位置就是環的入口。
參考csdn上的一張圖:


Paste_Image.png

設L1為無環長度,L2為環長,a為兩指針相遇時慢速指針在環上走過的距離,而且a一定小于環總長L2(這是因為當慢速指針剛進入環時,快速指針已經在環中,且距離慢速指針的距離最長為L2-1,需要追趕的距離為L2-1,即剛好在慢速指針的下一個節點,需要幾乎一整圈的距離來追趕,趕上時,慢速指針也不能走完一圈)。此時設慢速指針走過的節點數為N,則可列出:
快速指針走過的節點數為: 2N = L1 + k * L2 + a; (這里快速指針走過的節點數一定是慢速指針走過的2倍)。
慢速指針走過的節點數為: N = L1 + a;
則相減可得, N = k * L2 , 于是得到 k * L2 = L1 + a; 即, L1 = (k-1) * L2 + (L2 - a) (這里k至少是大于等于1的,因為快速指針至少要多走一圈)
即 L1的長度 = 環長的整數倍 + 相遇點到入口點的距離, 此時設置頭結點p3, 與p1同時,每次都走一步,相遇點即為入口點。

    /**
     *  方法二: 將其中一個鏈表首尾相連 從另外一個鏈表開始,檢測是否存在環,如果存在,則說明二者相交。
     *  如果需要找出環的入口,則設P1 P2 兩個指針,P1一次走兩步,P2一次走一步,兩者在環上某一點相遇。記下此位置。
     *  此時設置一個指針P3指向表頭,然后P1和P3每次同時行走一步,每步前進一個節點。等到P1、P3重合時,則重合位置即使環入口。
     * @param h1
     * @param h2
     * @return
     */
    public  DataNode entryNoLoop(DataNode h1,DataNode h2) {
        DataNode p = h1;
        while(null != p.getNext()){
            p = p.getNext();
        }
        p.setNext(h1);
        return entryLoop(h2);
    }
    
    /**
     * 獲取環的入口點
     * @param h
     * @return
     */
    public DataNode entryLoop(DataNode h) {
        DataNode p1 = h;
        DataNode p2 = h;
        DataNode p3 = h;
        
        while(null != p2.getNext() && null != p2.getNext().getNext()){
            p1 = p1.getNext();
            p2 = p2.getNext().getNext();
            if(p1 == p2)
                break;
        }
        while(p3 != p1) {
            p1 = p1.getNext();
            p3 = p3.getNext();
        }
        return p3;
    }
    

2.兩個鏈表均存在環

對于連個鏈表均存在環的情況,相交點要么在環上,要么在環外。

Paste_Image.png

無論上述何種情況,均需要首先分別找到各自到環的入口點。解法可以即使上述entryLoop方法。
在得到環的入口點之后,各自判斷環的入口點是否相同,如果如口點相同,則為左圖描述情況,因此只需計算著兩個鏈表到入口點部分長度之差,然后用長的部分減去差,再同時與短的部分同步前進,如果節點相同,則為相交點。反之如果入口點不同,則相交點為這兩個鏈表的任意一個入口點。

/**
     * 
     * @param h1
     *            鏈表1的頭節點
     * @param l1
     *            鏈表1的環入口
     * @param h2
     *            鏈表2的頭節點
     * @param l2
     *            鏈表2的頭節點
     * @return
     */
    public static DataNode bothLoop(DataNode h1, DataNode l1, DataNode h2, DataNode l2) {
        DataNode p1 = null;
        DataNode p2 = null;
        if (l1 == l2) {
            p1 = h1;
            p2 = h2;
            int n = 0;
            while (p1 != l1) {
                n++;
                p1 = p1.getNext();
            }
            while (p2 != l2) {
                n--;
                p2 = p2.getNext();
            }
            p1 = n > 0 ? h1 : h2;
            p2 = p1 == h1 ? h2 : h1;
            n = Math.abs(n);
            while (n != 0) {
                n--;
                h1 = h1.getNext();
            }
            while (p1 != p2) {
                p1 = p1.getNext();
                p2 = p2.getNext();
            }
            return p1;
        } else {
            p1 = l1.getNext();
            while (p1 != l1) {
                if (p1 == l2) {
                    return l1;
                }
            }
            return null;
        }
    }

    /**
     * 
     * @param h1
     * @param h2
     * @return
     */
    public DataNode getJoinNode(DataNode h1, DataNode h2) {
        if (null == h1 || null == h2)
            return null;
        DataNode l1 = entryLoop(h1);
        DataNode l2 = entryLoop(h2);
        if (null == l1 && null == l2)
            return getFirstJoinNode(h1, h2);
        if (null != l1 && null != l2)
            return bothLoop(h1,l1,h2,l2);
        return null;
    }

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,836評論 6 540
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,275評論 3 428
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,904評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,633評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,368評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,736評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,740評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,919評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,481評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,235評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,427評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,968評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,656評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,055評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,348評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,160評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,380評論 2 379

推薦閱讀更多精彩內容