面試——鏈表問題集錦

鏈表問題在面試過程當中也是很重要也很基礎的一部分,鏈表自己很靈活,很考查編程功底,因此是很值得考的地方。我將複習過程當中以爲比較好的鏈表問題整理了下。面試

下面是本文所要用到鏈表節點的定義:算法

struct Node{
    int data;
    Node* next;
};

1. 在O(1)時間刪除鏈表節點

題目描述:給定鏈表的頭指針和一個節點指針,在O(1)時間刪除該節點。[Google面試題]編程

分析:本題與《編程之美》上的「從無頭單鏈表中刪除節點」相似。主要思想都是「狸貓換太子」,即用下一個節點數據覆蓋要刪除的節點,而後刪除下一個節點。可是若是節點是尾節點時,該方法就行不通了。數據結構

代碼以下:(此代碼不適合要刪除的結點是尾結點)oop

Node *deleteNode(Node *head,Node *p)
{
    assert(head!=NULL);
    assert(p->next!=NULL);
    Node* post=p->next;
    p->data=post->data;
    p->next=post->next;
    delete post;
}

2. 單鏈表的轉置

題目描述:輸入一個單向鏈表,輸出逆序反轉後的鏈表post

分析:鏈表的轉置是一個很常見、很基礎的數據結構題了,非遞歸的算法很簡單,用三個臨時指針 pre、head、next 在鏈表上循環一遍便可。遞歸算法也是比較簡單的,可是若是思路不清晰估計一時半會兒也寫不出來吧。spa

下面是循環版本版本的鏈表轉置代碼:3d

void reverseList(Node *&head)
{
    if(head==NULL)
        return;
    Node *q=head->next;
   head->next=NULL;
while(q) { Node *p=q->next; q->next=head; head=q; q=p; } return head; }

3. 求鏈表倒數第k個節點

題目描述:輸入一個單向鏈表,輸出該鏈表中倒數第k個節點,鏈表的倒數第0個節點爲鏈表的尾指針。指針

分析:設置兩個指針 p一、p2,首先 p1 和 p2 都指向 head,而後 p2 向前走 k 步,這樣 p1 和 p2 之間就間隔 k 個節點,最後 p1 和 p2 同時向前移動,直至 p2 走到鏈表末尾。code

代碼以下:

Node* findK(Node* head,int k)
{
    if(head==NULL||k<=0)
        return NULL;
    int count=0;
    Node *p=head;
    Node *q=head;
    while(q!=NULL&&count<k)
    {
        q=q->next;
        count++;
    }
    if(count<k)
        return NULL;
    while(q)
    {
        p=p->next;
        q=q->next;
    }
    return p;
}

4. 求鏈表的中間節點

題目描述:求鏈表的中間節點,若是鏈表的長度爲偶數,返回中間兩個節點的任意一個,若爲奇數,則返回中間節點。

分析:此題的解決思路和第3題「求鏈表的倒數第 k 個節點」很類似。能夠先求鏈表的長度,而後計算出中間節點所在鏈表順序的位置。可是若是要求只能掃描一遍鏈表,如何解決呢?最高效的解法和第3題同樣,經過兩個指針來完成。用兩個指針從鏈表頭節點開始,一個指針每次向後移動兩步,一個每次移動一步,直到快指針移到到尾節點,那麼慢指針便是所求。

代碼以下:

//求鏈表的中間節點
Node* theMiddleNode(Node *head)
{
    if(head == NULL)
        return NULL;
    Node *slow,*fast;
    slow = fast = head;
    //若是要求在鏈表長度爲偶數的狀況下,返回中間兩個節點的第一個,能夠用下面的循環條件
    //while(fast && fast->next != NULL && fast->next->next != NULL)  
    while(fast != NULL && fast->next != NULL)
    {
        fast = fast->next->next;
        slow = slow->next;
    }
    return slow;
}

5. 判斷單鏈表是否存在環

題目描述:輸入一個單向鏈表,判斷鏈表是否有環?

分析:經過兩個指針,分別從鏈表的頭節點出發,一個每次向後移動一步,另外一個移動兩步,兩個指針移動速度不同,若是存在環,那麼兩個指針必定會在環裏相遇。

代碼以下:

//判斷單鏈表是否存在環,參數circleNode是環內節點,後面的題目會用到
bool hasCircle(Node *head,Node *&circleNode)
{
    Node *slow,*fast;
    slow = fast = head;
    while(fast != NULL && fast->next != NULL)
    {
        fast = fast->next->next;
        slow = slow->next;
        if(fast == slow)
        {
            circleNode = fast;
            return true;
        }
    }

    return false;
}

6. 找到環的入口點

題目描述:輸入一個單向鏈表,判斷鏈表是否有環。若是鏈表存在環,如何找到環的入口點?

解題思路: 由上題可知,按照 p2 每次兩步,p1 每次一步的方式走,發現 p2 和 p1 重合,肯定了單向鏈表有環路了。接下來,讓p2回到鏈表的頭部,從新走,每次步長不是走2了,而是走1,那麼當 p1 和 p2 再次相遇的時候,就是環路的入口了。

爲何?:假定起點到環入口點的距離爲 a,p1 和 p2 的相交點M與環入口點的距離爲b,環路的周長爲L,當 p1 和 p2 第一次相遇的時候,假定 p1 走了 n 步。那麼有:

p1走的路徑: a+b = n
p2走的路徑: a+b+k*L = 2*n; p2 比 p1 多走了k圈環路,總路程是p1的2倍

根據上述公式能夠獲得 k*L=a+b=n顯然,若是從相遇點M開始,p1 再走 n 步的話,還能夠再回到相遇點,同時p2從頭開始走的話,通過n步,也會達到相遇點M。

顯然在這個步驟當中 p1 和 p2 只有前 a 步走的路徑不一樣,因此當 p1 和 p2 再次重合的時候,必然是在鏈表的環路入口點上。

代碼以下:

//找到環的入口點
Node* findLoopPort(Node *head)
{
    Node *slow,*fast;
    slow = fast = head;

    //先判斷是否存在環
    while(fast != NULL && fast->next != NULL)
    {
        fast = fast->next->next;
        slow = slow->next;
        if(fast == slow)
            break;
    }

    if(fast != slow) return NULL;    //不存在環

    fast = head;                //快指針從頭開始走,步長變爲1
    while(fast != slow)            //二者相遇即爲入口點
    {
        fast = fast->next;
        slow = slow->next;
    }

    return fast;
}

7. 編程判斷兩個鏈表是否相交

題目描述:給出兩個單向鏈表的頭指針(以下圖所示),

好比h一、h2,判斷這兩個鏈表是否相交。這裏爲了簡化問題,咱們假設兩個鏈表均不帶環。

解題思路:

  1. 直接循環判斷第一個鏈表的每一個節點是否在第二個鏈表中。但,這種方法的時間複雜度爲O(Length(h1) * Length(h2))。顯然,咱們得找到一種更爲有效的方法,至少不能是O(N^2)的複雜度。

  2. 針對第一個鏈表直接構造hash表,而後查詢hash表,判斷第二個鏈表的每一個節點是否在hash表出現,若是全部的第二個鏈表的節點都能在hash表中找到,即說明第二個鏈表與第一個鏈表有相同的節點。時間複雜度爲爲線性:O(Length(h1) + Length(h2)),同時爲了存儲第一個鏈表的全部節點,空間複雜度爲O(Length(h1))。是否還有更好的方法呢,既可以以線性時間複雜度解決問題,又能減小存儲空間?

  3. 轉換爲環的問題。把第二個鏈表接在第一個鏈表後面,若是獲得的鏈表有環,則說明兩個鏈表相交。如何判斷有環的問題上面已經討論過了,但這裏有更簡單的方法。由於若是有環,則第二個鏈表的表頭必定也在環上,即第二個鏈表會構成一個循環鏈表,咱們只須要遍歷第二個鏈表,看是否會回到起始點就能夠判斷出來。這個方法的時間複雜度是線性的,空間是常熟。

  4. 進一步考慮「若是兩個沒有環的鏈表相交於某一節點,那麼在這個節點以後的全部節點都是兩個鏈表共有的」這個特色,咱們能夠知道,若是它們相交,則最後一個節點必定是共有的。而咱們很容易能獲得鏈表的最後一個節點,因此這成了咱們簡化解法的一個主要突破口。那麼,咱們只要判斷兩個鏈表的尾指針是否相等。相等,則鏈表相交;不然,鏈表不相交。
    因此,先遍歷第一個鏈表,記住最後一個節點。而後遍歷第二個鏈表,到最後一個節點時和第一個鏈表的最後一個節點作比較,若是相同,則相交,不然,不相交。這樣咱們就獲得了一個時間複雜度,它爲O((Length(h1) + Length(h2)),並且只用了一個額外的指針來存儲最後一個節點。這個方法時間複雜度爲線性O(N),空間複雜度爲O(1),顯然比解法三更勝一籌。

解法四的代碼以下:

//判斷兩個鏈表是否相交
bool isIntersect(Node *h1,Node *h2)
{
    if(h1 == NULL || h2 == NULL) return false;    //異常判斷
    while(h1->next != NULL)
    {
        h1 = h1->next;
    }

    while(h2->next != NULL)
    {
        h2 = h2->next;
    }

    if(h1 == h2) return true;        //尾節點是否相同
    else return false;
}

8. 擴展:鏈表有環,如何判斷相交

題目描述:上面的問題都是針對鏈表無環的,那麼若是如今,鏈表是有環的呢?上面的方法還一樣有效麼?

分析:若是有環且兩個鏈表相交,則兩個鏈表都有共同一個環,即環上的任意一個節點都存在於兩個鏈表上。所以,就能夠判斷一鏈表上倆指針相遇的那個節點,在不在另外一條鏈表上。

代碼以下:

//判斷兩個帶環鏈表是否相交
bool isIntersectWithLoop(Node *h1,Node *h2)
{
    Node *circleNode1,*circleNode2;
    if(!hasCircle(h1,circleNode1))    //判斷鏈表帶不帶環,並保存環內節點
        return false;                //不帶環,異常退出
    if(!hasCircle(h2,circleNode2))
        return false;

    Node *temp = circleNode2->next;
    while(temp != circleNode2)
    {
        if(temp == circleNode1)
            return true;
        temp = temp->next;
    }
    return false;
}

9. 擴展:兩鏈表相交的第一個公共節點

題目描述:若是兩個無環單鏈表相交,怎麼求出他們相交的第一個節點呢?

分析:採用對齊的思想。計算兩個鏈表的長度 L1 , L2,分別用兩個指針 p1 , p2 指向兩個鏈表的頭,而後將較長鏈表的 p1(假設爲 p1)向後移動L2 - L1個節點,而後再同時向後移動p1 , p2,直到 p1 = p2。相遇的點就是相交的第一個節點。

代碼以下:

//求兩鏈表相交的第一個公共節點
Node* findIntersectNode(Node *h1,Node *h2)
{
    int len1 = listLength(h1);          //求鏈表長度
    int len2 = listLength(h2);
    //對齊兩個鏈表
    if(len1 > len2)
    {
        for(int i=0;i<len1-len2;i++)
            h1=h1->next;
    }
    else 
    {
        for(int i=0;i<len2-len1;i++)
            h2=h2->next;
    }

    while(h1 != NULL)
    {
        if(h1 == h2)
            return h1;
        h1 = h1->next;
        h2 = h2->next;    
    }
    return NULL;
}

10. 總結

能夠發現,在鏈表的問題中,經過兩個的指針來提升效率是很值得考慮的一個解決方案,因此必定要記住這種解題思路。記住幾種典型的鏈表問題解決方案,不少相似的題目均可以轉換到熟悉的問題再解決。

相關文章
相關標籤/搜索