九章算法系列(#5 Linked List)-課堂筆記

前言

又是很長時間纔回來發一篇博客,前一個月確實由於雜七雜八的事情影響了不少,如今仍是到了大火燃眉毛的時候了,也應該開始繼續整理一下算法的思路了。Linked List你們應該是特別熟悉不過的了,由於這個算是數據結構了裏面基本上最開始講的結構吧。這塊內容也沒有太多須要琢磨的技巧,能夠考量的東西也很少,因此考的就是一些小的trick來完成,面試中鏈表考得特別多,算是面試官對面試者的基礎的考查,因此我建議你們在Linked List這一章,必定要實現Bug Free。這個也是我練的比較多的,有些想法能夠和你們分享。node

 

outline:

  • Dummy Node in Linked List
    • Remove Duplicates from Sorted List II
    • Reverse Linked List II
    • Partition List
  • Basic Linked List Skills
    • Sort List
    • Reorder List
  • Two Pointers in Linked List (Fast-slow pointers)
    • Merge K Sorted Lists

 

課堂筆記


1. Dummy Node in Linked List面試

有不少時候,咱們須要對整個鏈表進行操做,這樣會致使鏈表的結構發生變化,或者當須要返回的鏈表頭是不肯定的時候,咱們就須要用一個Dummy Node來存那個開始的「頭」,最後返回的也是這個「頭」。這樣就不須要單獨對head進行操做了,第一個題目以下:算法

Remove Duplicates from Sorted List IIchrome

給定一個排序鏈表,刪除全部重複的元素只留下原鏈表中沒有重複的元素。數據結構

樣例ide

給出 1->2->3->3->4->4->5->null,返回 1->2->5->nullpost

給出 1->1->1->2->3->null,返回 2->3->nullspa

這個題算是比較簡單的題目,就是考慮鏈表的刪除操做。可是若是不用dummy的話,可能還須要單獨考慮head是否爲重複元素,部分代碼以下:翻譯

int val = head->val;
while (head->next && head->next->val == val) {
    head->next = head->next->next;
}
head = head->next;

這樣就很麻煩,代碼不夠簡潔,並且可能在某些地方出現問題。因此這裏就引入dummy的方法(這塊內容必定要朗讀並背誦全文)(Bug Free):3d

    ListNode * deleteDuplicates(ListNode *head) {
        // write your code here
        if (!head || !head->next) {
            return head;
        }
        ListNode dummy = ListNode(0);
        dummy.next = head;
        head = &dummy;
        while (head->next && head->next->next) {
            if (head->next->val == head->next->next->val) {
                int val = head->next->val;
                while (head->next && head->next->val == val) {
                    head->next = head->next->next;
                }
            } else {
                head = head->next;
            }
        }
        return dummy.next;
    }
View Code

這裏的思想比較簡單,就是用一個dummy存入一開始head的地址,而後再把head指向dummy,這樣就至關於整個鏈表從head->next開始了,而後進行相應的判斷就好。最後返回dummy.next即爲鏈表當前的head。其實就是一個小技巧,你們就能夠把head當成一個p節點,直接使用就好。

接下來一個題:

Reverse Linked List II

翻轉鏈表中第m個節點到第n個節點的部分

樣例

給出鏈表1->2->3->4->5->null, m = 2 和n = 4,返回1->4->3->2->5->null

相信你們在面試的時候被問到鏈表反轉的時候必定是最高興的,由於這時候你能夠5分鐘把bug free的代碼寫出來。這道題稍微增長了一點點難度,就是反轉從m到n的節點,其餘的不變。其實也不難,就是保留一下關鍵的點,而後其餘地方同樣進行反轉就能夠。這個題也是爲了講解一下dummy,因此放出來,代碼以下(Bug Free):

    ListNode *reverseBetween(ListNode *head, int m, int n) {
        // write your code here
        if (!head || !head->next) {
            return head;
        }
        ListNode dummy = ListNode(0);
        dummy.next = head;
        head = &dummy;
        for (int i = 1; i < m; ++i) {
            head = head->next;
        }
        // 保存m前一個節點
        ListNode *pre = head;
        // 保存第m個節點
        ListNode *mNode = head->next;
        // 保存第n個節點
        ListNode *nNode = mNode;
        // 保存n的後一個節點
        ListNode *post = mNode->next;
        for (int i = m; i < n; ++i) {
            ListNode *tmp = post->next;
            post->next = nNode;
            nNode = post;
            post = tmp;
        }
        mNode->next = post;
        pre->next = nNode;
        return dummy.next;
    }
View Code

這裏就很少解釋了。

這個小節主要就是讓你們熟悉掌握dummy的用法,這樣可以大量介紹代碼量,而且給面試加分很多。

仍是記住兩個點:

  • 當鏈表的結構發生變化時
  • 當須要返回的鏈表頭不肯定時

這兩種狀況下是須要使用dummy的。

這裏最後來一個題:

Partition List

給定一個單鏈表和數值x,劃分鏈表使得全部小於x的節點排在大於等於x的節點以前。

你應該保留兩部份內鏈表節點原有的相對順序。

樣例

給定鏈表 1->4->3->2->5->2->null,而且 x=3

返回 1->2->2->4->3->5->null

其實這道題是最能體現dummy的做用的。咱們就遍歷整個鏈表,比x小的放入左邊的鏈表,不然放入右邊的鏈表,最後再把兩個鏈表合在一塊兒。這時候使用dummy就不須要再去找各自的頭指針了。看似簡單,可是最後我仍是沒有實現bug free,關鍵的地方就是:當遍歷到最後一個節點的時候,若是這個數比x小,那麼須要把它向前移動,此時就須要把右邊鏈表的尾節點指向空,不然就會出現LTE,這個是很是關鍵的問題,須要你們重視。代碼以下:

    ListNode *partition(ListNode *head, int x) {
        // write your code here
        if (!head || !head->next) {
            return head;
        }
        ListNode dummy1 = ListNode(0);
        ListNode dummy2 = ListNode(0);
        ListNode *pre = &dummy1;
        ListNode *post = &dummy2;
        while (head) {
           if (head->val < x) {
               pre->next = head;
               pre = head;
           } else {
               post->next = head;
               post = head;
           }
           head = head->next;
        }
        // 最後的節點指向空,不然出現死循環
        post->next = NULL;
        pre->next = dummy2.next;
        return dummy1.next;
    }
View Code

2. Basic Skills in Linked List

說到鏈表的基本技巧,插入、刪除、翻轉這三個你們應該都很熟悉了,而後還有兩個比較麻煩的操做就是合併兩個鏈表或是中分兩個鏈表(這裏用這樣的翻譯求不吐槽,原文:middle of a linked list),能夠說這兩個技巧要是充分掌握了的話,基本上鍊表的題目你也不須要擔憂了。

直接來一個題吧:

Sort List

在 O(n log n) 時間複雜度和常數級的空間複雜度下給鏈表排序。

樣例

給出 1->3->2->null,給它排序變成 1->2->3->null.

這個思路就不須要我講了,接下來我將用歸併排序來作,這裏不適用快速排序是由於大量的操做對於鏈表來講耗時太大了,因此就不考慮了。

歸併排序(Bug Free):

    ListNode *merge(ListNode *left, ListNode *right) {
        ListNode dummy = ListNode(0);
        ListNode *res = &dummy;
        while (left && right) {
            if (left->val < right->val) {
                res->next = left;
                left = left->next;
            } else {
                res->next = right;
                right = right->next;
            }
            res = res->next;
        }
        if (left) {
            res->next = left;
        } else {
            res->next = right;
        }
        return dummy.next;
    }
    ListNode *sortList(ListNode *head) {
        // write your code here
        if (!head || !head->next) {
            return head;
        }
        ListNode *fast = head->next;
        ListNode *slow = head;
        while(fast && fast->next) {
            fast = fast->next->next;
            slow = slow->next;
        }
        ListNode * right = sortList(slow->next);
        // 這裏很是關鍵,必定要把左邊結尾指向空
        slow->next = NULL;
        ListNode * left = sortList(head);
        return merge(left,right);
    }
View Code

接下來的一個題在面試中見到過,算是比較簡單,可是容易出錯的題目:

Reorder List

給定一個單鏈表L: L0→L1→…→Ln-1→Ln,

從新排列後爲:L0→Ln→L1→Ln-1→L2→Ln-2→…

必須在不改變節點值的狀況下進行原地操做。

樣例

給出鏈表 1->2->3->4->null,從新排列後爲1->4->2->3->null

這樣的題其實看上去比較複雜,可是你能夠結合一下我前面講過的幾個題,就是一種很是簡單的作法就能夠完成了。咱們首先找到鏈表的中點,而後把右半段鏈表翻轉,以後再進行合併就能夠,代碼以下(Bug Free):

    void merge(ListNode *left, ListNode *right) {
        while (left && right) {
            ListNode *node1 = left->next;
            ListNode *node2 = right->next;
            left->next = right;
            right->next = node1;
            left = node1;
            right = node2;
        }
    }
    void reorderList(ListNode *head) {
        // write your code here
        if (!head || !head->next) {
            return;
        }
        ListNode *fast = head->next;
        ListNode *slow = head;
        while (fast && fast->next) {
            fast = fast->next->next;
            slow = slow->next;
        }
        ListNode *right = slow->next;
        slow->next = NULL;
        ListNode *ntr = NULL;
        while (right && right->next) {
            ListNode *tmp = right->next;
            right->next = ntr;
            ntr = right;
            right = tmp;
        }
        right->next = ntr;
        merge(head,right);
    }
View Code

這個題關鍵仍是要細心,應該不會出什麼問題的。


3.fast-slow pointers

寫了半天,忽然chrome就崩潰了,真是好心塞。

這個由於以前也講過,因此直接來作一個比較重要的題目吧:

Merge K Sorted Lists

合併k個排序鏈表,而且返回合併後的排序鏈表。嘗試分析和描述其複雜度。

這個題目能夠用優先隊列來作,也能夠兩兩合併而後合在一塊兒,這裏我才用的是分治法來說解:把lists分解爲k/2的規模,而後繼續分解,直至兩兩合併,思路比較簡單,代碼以下

    ListNode *mergeTwoLists(ListNode *left, ListNode *right) {
        ListNode dummy = ListNode(0);
        ListNode *res = &dummy;
        while(left && right) {
            if (left->val < right->val) {
                res->next = left;
                left = left->next;
            } else {
                res->next = right;
                right = right->next;
            }
            res = res->next;
        }
        if (left) {
            res->next = left;
        } else {
            res->next = right;
        }
        return dummy.next;
    }
    
    ListNode *mergeHelper(vector<ListNode *> &lists, int start, int end) {
        if (start == end) {
            return lists[start];
        }
        int mid = start + (end - start) / 2;
        ListNode *left = mergeHelper(lists, start, mid);
        ListNode *right = mergeHelper(lists, mid + 1, end);
        return mergeTwoLists(left,right);
    }
    
    ListNode *mergeKLists(vector<ListNode *> &lists) {
        // write your code here
        if (!lists.size()) {
            return NULL;
        }
        return mergeHelper(lists, 0, lists.size() - 1);
    }
View Code

總結

由於鏈表算是比較基礎的內容了,因此也不須要太多的東西。你們就把幾個重要的trick掌握好就能夠了,仍是須要多多練習。

知識點以下:

1.dummy的使用

2.fast-slow pointer的使用

相關文章
相關標籤/搜索