單鏈表問題總結

參考資料:
1. Leetcode
2. 【Hackbuteer1的專欄】http://blog.csdn.net/hackbuteer1/article/details/7583102
最近做了些單鏈表的題目,覺得要解決單鏈表問題,可以從以下幾個方面入手:
先定義鏈表的結構

class ListNode {
    int val;
    ListNode next;
    ListNode(int x) { val = x; }
 }

1. 要學會畫草圖進行模擬
2. 要會鏈表的逆置
鏈表的逆置是許多其他鏈表題目的基礎,給定一個鏈表,我們可以採用頭插法的方法對這個鏈表進行逆置,代碼如下:

    /**
     * 逆轉鏈表
     * */
    public static ListNode reverseListNode(ListNode node){
        ListNode reverseHead=null;//逆轉後鏈表的頭結點
        ListNode moveNode=null;
        while(node!=null){
            moveNode=node.next; //保存待逆置的鏈表的下一個節點
            if(reverseHead==null){ //第一個節點即已逆置的鏈表的尾節點
                reverseHead=node;
                reverseHead.next=null;
            }else{ //採用頭插法建立鏈表
                node.next=reverseHead;
                reverseHead=node;
            }
            node=moveNode;
        }
        return reverseHead;
    }

以鏈表 1->2->3 爲例子,模擬如下:
這裏寫圖片描述

學會了逆置後,我們可以做做這個題https://leetcode.com/problems/palindrome-linked-list/,就是判斷一個鏈表裏的內容是不是迴文字符串,要求O(n)的時間複雜度和O(1)的空間複雜度。我們的做法可以是:先遍歷一遍鏈表,求出鏈表的長度L,然後從L/2即中間節點的位置將後半段鏈表逆置,再依次遍歷逆置的鏈表以及前半段鏈表,進行比較就可以判斷是不是迴文字符串了。

3. 容器比如Set或Stack很好用,如果在對空間複雜度沒有要求的話,可以考慮使用容器
有時候沒有要求空間複雜度,我們可以藉助一些集合來處理,因爲Set具有元素唯一的特性即其不能存儲重複的元素,而棧具備先進後出的特性。這裏針對之前的判斷迴文字符串的問題,我們可以使用一個棧:先遍歷一遍鏈表,將所有節點入棧,之後再次遍歷鏈表並且出棧,進行比較判斷,也能求得鏈表是不是迴文字符串,這裏時間複雜度也是O(n),但其空間複雜度也是O(n)。
使用Set可以很好的解決鏈表存不存在環的問題,比如給定一個單鏈表,判斷鏈表是否存在環,如果存在環求出環的入口節點。針對這種題目,我們使用一個Set,然後從鏈表頭節點開始add,由於Set的add方法在添加重複的元素時返回false,因此針對鏈表有兩種情況:
1. 鏈表不含有環:那麼可以一直add,不會返回false,直到鏈表結尾
2. 鏈表含有環:那麼在不停的add過程中會進入到環裏,肯定會add之前已經add的節點,此時會返回false,而且第一次返回false時對應的節點就是環的入口節點,可以畫個草圖模擬下,代碼如下:

    public boolean hasCycle(ListNode head) {
        Set<ListNode> nodeSet=new HashSet<>();
        while(head!=null){
            if(!nodeSet.add(head)) //如果添加不成功,說明之前已經存在該節點,即有環,此時這個head節點即爲環的入口節點
                return true;
            head=head.next;
        }
        return false;
    }

如果針對求環的問題要求空間複雜度爲O(1)呢?我們就不能使用Set了,可以使用下面介紹的兩個指針的方法

4. 要學會使用兩個指針
有時間針對一個鏈表我們需要使用兩個指針來實現所謂的“追趕”問題,針對兩個鏈表則更需要兩個指針了,我們先使用兩個指針來解決判斷兩個鏈表是否相交的鏈表,如下圖所示:
這裏寫圖片描述
方法1:先計算L1,L2的長度爲S1,S2,假設S1大於S2(反之亦然),設立兩個指針指向L1,L2,L1先走(S1-S2)步,此時L1,L2是處於同一個位置的,然後同時移動,如果鏈表相交必然會存在L1==L2,否則鏈表相交,這裏的時間複雜度是O(max(S1,S2)),空間複雜度爲O(1)
方法2:將兩個鏈表分別逆置,判斷逆置後兩個鏈表的頭節點是否相等,相等則兩個鏈表相交,否則不相交。
方法3:可以將任一鏈表的尾節點的後繼指向另一個鏈表的頭節點,此時就變成了判斷這個鏈表是否存在環的問題
將兩個指針最恰當的運行就是在O(1)空間複雜度判斷一個鏈表是否存在環,如果存在環找出環的入口節點,這裏說下簡要的步驟,具體細節可參考開頭引用的博客。
判斷鏈表是否存在環:設立兩個指針slow,fast,開始均指向鏈表的頭結點,接着slow每次走一步,fast每次走兩步,如果不存在環fast肯定先爲NULL,如果存在環,fast繞環n圈後肯定會追上slow,即fast==slow
找出環的入口節點:根據前面得到了fast與slow第一次相遇的節點p,重新設立指針p1,p2。p1指向鏈表頭結點,p2指向p。p1,p2同時移動相同的步數,那麼p1,p2第一相遇的節點即爲環的入口節點,至於爲什麼,相關的證明可以上面引用的博客。

總結:我覺得鏈表題不算特別難,主要是自己得細心,考慮一些極端情況(比如指針爲NULL之類的);另外就是在處理鏈表的時候,注意各個引用的關係(最好畫草圖),別把節點的指向搞混了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章