面試經典算法-鏈表反轉、鏈表合併等

01 認識『鏈表』

鏈表的結構十分簡單,其本身是一種線性的存儲結構,通過物理地址不連續的節點相連接成鏈。最簡單的單鏈表只包含一條鏈,並且每一個節點包括兩部分內容,數據元素和下一個節點的地址。因此可通過已知節點訪問它的下一個節點。
在這裏插入圖片描述
相比於數組而言,由於鏈表不必須按順序存儲,因而在插入的時候可以達到O(1)的複雜度,一般而言比數組的效率要高得多;但是查找一個節點或者訪問特定編號的節點則需要O(n)的時間,而數組由於可以直接通過下標尋址,相應的時間複雜度僅爲O(1)。
一般可通過定義結構體的方式來實現鏈表:

// Definition for singly-linked list.
 struct ListNode {
      int val;
      ListNode *next;
      ListNode(int x) : val(x), next(NULL) {}
 };

02 高頻算法題

1. 輸入一個鏈表,輸出該鏈表中倒數第k個結點。

快慢指針的方法在鏈表相關的操作中經常使用。
通過設置快慢指針,快指針先前進k步,之後快慢指針同時前進,此時快慢指針間隔k步;當快指針到達鏈表尾部,此時慢指針所在節點即爲倒數第k個節點。這樣的方法僅通過一次遍歷即可獲得倒數第k個節點。
代碼如下:


class Solution {
public:
    ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) {
        if(pListHead==NULL||k==0)
            return NULL;
        ListNode*pTail=pListHead,*pHead=pListHead;
        for(int i=1;i<k;++i)
        {
            if(pHead->next!=NULL)
                pHead=pHead->next;
            else
                return NULL;
        }
        while(pHead->next!=NULL)
        {
            pHead=pHead->next;
            pTail=pTail->next;
        }
        return pTail;
    }
};

2. 輸入一個鏈表,反轉鏈表後,輸出新鏈表的表頭。

非遞歸方法的解題思路需要理解以下四步,具體見下圖:
在這裏插入圖片描述
代碼如下:


//第一種方法:非遞歸方法 /*
class Solution {
public:
    ListNode* ReverseList(ListNode* pHead) {
        if(pHead==NULL)
            return NULL;
        
        ListNode* cur = pHead;
        ListNode* pre = NULL;
        ListNode* head = NULL;
        
        while(cur != NULL)
        {
            if(cur->next==NULL)
                head = cur;
            ListNode* nxt = cur->next;
            cur->next = pre;
            pre = cur;
            cur = nxt;
            
        }
        return head;
    }
};

//第二種方法是:遞歸方法 /*
class Solution {
public:
    ListNode* ReverseList(ListNode* pHead) {
        //如果鏈表爲空或者鏈表中只有一個元素
        if(pHead==NULL||pHead->next==NULL) return pHead;
         
        //先反轉後面的鏈表,走到鏈表的末端結點
        ListNode* pReverseNode=ReverseList(pHead->next);
         
        //再將當前節點設置爲後面節點的後續節點
        pHead->next->next=pHead;
        pHead->next=NULL;
        return pReverseNode;    
}
};

3. 將兩個有序鏈表合併爲一個新的有序鏈表並返回。

這題可通過設置雙指針的方式對鏈表不同節點元素進行篩選:
在這裏插入圖片描述
代碼如下:


class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        if (l1 == null) {
            return l2;
        }
        if (l2 == null) {
            return l1;
        }
        if (l1.val <= l2.val) {
            l1.next = mergeTwoLists(l1.next, l2);
            return l1;
        } else {
            l2.next = mergeTwoLists(l1, l2.next);
            return l2;
        }
    }
}

4. 尋找兩個相交鏈表的公共起點。

這題同樣可以設置快慢指針,比較常規的方法可以先求得2個鏈表的長度,然後讓長鏈表指針先前進兩個鏈表的長度差,然後再一起前進;最後當快慢指針相遇的位置即是公共起點。
代碼如下:


class Solution {
public:
    ListNode* FindFirstCommonNode( ListNode *pHead1, ListNode *pHead2) {
        int len1 = findListLenth(pHead1);
        int len2 = findListLenth(pHead2);
        if(len1 > len2){
            pHead1 = walkStep(pHead1,len1 - len2);
        }else{
            pHead2 = walkStep(pHead2,len2 - len1);
        }
        while(pHead1 != NULL){
            if(pHead1 == pHead2) return pHead1;
            pHead1 = pHead1->next;
            pHead2 = pHead2->next;
        }
        return NULL;
    }
     
    int findListLenth(ListNode *pHead1){
        if(pHead1 == NULL) return 0;
        int sum = 1;
        while(pHead1 = pHead1->next) sum++;
        return sum;
    }
     
    ListNode* walkStep(ListNode *pHead1, int step){
        while(step--){
            pHead1 = pHead1->next;
        }
        return pHead1;
    }
};

5. 給一個鏈表,若其中包含環,請找出該鏈表的環的入口結點,否則,輸出null。

在這裏插入圖片描述
這題同樣可採用快慢指針的方法來做。

快指針每次前進2步,慢指針每次前進1步。若鏈表有環,則快慢指針會在環內相遇。假設圖中b是環的入口,a爲鏈表起點,c爲快慢指針相遇的位置。由於快慢指針的前進時間相同,則根據速度關係可得:

2(ab+bc)=ab+bc+cb+bc,則得ab=bc
在這裏插入圖片描述
所以當快慢指針相遇後,從相遇點到環入口的距離與從鏈表頭到環入口的距離一樣。通過設置一指針從鏈表頭部前進,一指針從相遇點同時前進,兩指針相遇的位置即是鏈表環的入口。

代碼如下:

class Solution {
public:
    ListNode* EntryNodeOfLoop(ListNode* pHead)
    {
        ListNode*fast=pHead,*low=pHead;
        while(fast&&fast->next){
            fast=fast->next->next;
            low=low->next;
            if(fast==low)
                break;
        }
        if(!fast||!fast->next)
            return NULL;
        low=pHead;//low從鏈表頭出發
        while(fast!=low){//fast從相遇點出發
            fast=fast->next;
            low=low->next;
        }
        return low;
    }
};

ps:個人公衆號【業餘碼農】,每天更新互聯網面試經驗分享,還有技術基礎整理和常見算法詳解。
在這裏插入圖片描述

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