新冠肺炎肆虐,不如在家練練基礎算法 | 鏈表篇

新冠肺炎肆虐,不如在家練練基礎算法 | 鏈表篇

今年春節,新型冠狀肺炎以迅雷不及掩耳盜鈴兒響叮噹之勢迅速席捲了全國各地。我所在的大學正處於疫情事件的中心武漢,所以更加深入的感覺到這一場突如其來的國家公共衛生事件的嚴重性。面試

從幾天前武漢市全面封城開始,全國從上到下都在推行不出門、不聚會的號召。算法

今年春節或許成爲近幾年來最爲「冷清」的春節。在這個節骨眼上,也終於到了啥也不幹躺在家裏就能給社會作貢獻的時候。也正是由於疫情的嚴重性,國家和各大公司相繼推遲春節假期和工做開學的時間。數組

對我的而言,或許在爲社會作貢獻的同時,也能夠趁機提高本身,從而能夠爲迎接下一次面試而作好準備。數據結構

在互聯網招聘的面試環節中,手撕算法環節每每會與數據結構的考察相結合。各類經典的算法都離不開經常使用數據結構的支持。ide

本文將從最爲基礎的數據結構-鏈表出發,從leetcode中選擇幾道表明性的題目進行講解,從而讓同窗們可以對鏈表的基本操做和變形算法有比較全面的瞭解和認識。oop

01
認識『鏈表』指針

鏈表的結構十分簡單,其自己是一種線性的存儲結構,經過物理地址不連續的節點相鏈接成鏈。最簡單的單鏈表只包含一條鏈,而且每個節點包括兩部份內容,數據元素和下一個節點的地址。所以可經過已知節點訪問它的下一個節點。
新冠肺炎肆虐,不如在家練練基礎算法 | 鏈表篇code

相比於數組而言,因爲鏈表沒必要須按順序存儲,於是在插入的時候能夠達到O(1)的複雜度,通常而言比數組的效率要高得多;可是查找一個節點或者訪問特定編號的節點則須要O(n)的時間,而數組因爲能夠直接經過下標尋址,相應的時間複雜度僅爲O(1)。blog

通常可經過定義結構體的方式來實現鏈表:排序

// 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;
    }
};
  1. 輸入一個鏈表,反轉鏈表後,輸出新鏈表的表頭。

非遞歸方法的解題思路須要理解如下四步,具體見下圖:

新冠肺炎肆虐,不如在家練練基礎算法 | 鏈表篇
代碼以下:
//第一種方法:非遞歸方法 /
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;

}
};

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

這題可經過設置雙指針的方式對鏈表不一樣節點元素進行篩選:
新冠肺炎肆虐,不如在家練練基礎算法 | 鏈表篇
代碼以下:

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;
        }
    }
}
  1. 尋找兩個相交鏈表的公共起點。

這題一樣能夠設置快慢指針,比較常規的方法能夠先求得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;
    }
};
  1. 給一個鏈表,若其中包含環,請找出該鏈表的環的入口結點,不然,輸出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;
    }
};

03
總結

鏈表相關的算法在面試中常常考察,其考察的重點在於對鏈表結構熟練的操做,包括指針的切換、節點的增刪和變形等等。

上面所舉的幾個例子都是對鏈表比較基本的處理,能夠從中理解到鏈表經常使用的快慢指針、遍歷以及邊界判斷等問題。

另外,面試中出現的鏈表相關的高頻算法題目可參考如下Leetcode題號:

Leetcode 19 刪除鏈表的倒數第N個節點
Leetcode 21 合併兩個有序鏈表
Leetcode 23 合併K個排序鏈表
Leetcode 25 K個一組翻轉鏈表
Leetcode 86 分隔鏈表
Leetcode 109 有序鏈表轉換二叉樹
Leetcode 138 複製帶隨機指針的鏈表
Leetcode 141 環形鏈表
Leetcode 160 相交鏈表
Leetcode 206 反轉鏈表

掃個關注不迷路,校招面試定不誤。
新冠肺炎肆虐,不如在家練練基礎算法 | 鏈表篇

推薦閱讀(點擊下方連接便可閱讀)

苦逼研究生的幸運求職路
建議簡歷寫很差的同窗進來瞧一瞧~
非科班如何經過業餘時間自學遊戲開發,最終收穫騰訊網易offer
面試高頻算法詳解-LRU
生物專業女生教你準備兩個月簽約AI獨角獸
想成爲BAT後臺開發工程師,這些是基礎!

Amazing10承蒙厚愛。

相關文章
相關標籤/搜索