1. 程式人生 > >玩轉演算法面試:(五)LeetCode連結串列類問題

玩轉演算法面試:(五)LeetCode連結串列類問題

在連結串列中穿針引線

連結串列和陣列都是線性結構,但是連結串列和陣列的不同在於陣列可以隨機的對於資料進行訪問。給出索引。可以以O(1)的時間複雜度迅速訪問到該元素。
連結串列只能從頭指標開始。

next指標指向哪裡?

206. Reverse Linked List

反轉一個連結串列

連結串列反轉

不能改變連結串列值。操作每個節點的next指標。
5的指標指向空的。變成指向4的。

next改變示意圖

把1指向NUll之前要儲存next指標

新增pre指標儲存上一個節點

// 時間複雜度: O(n)
// 空間複雜度: O(1)
class Solution {
public:
    ListNode* reverseList(ListNode* head) {

        ListNode* pre = NULL;
        ListNode* cur = head;
        while( cur != NULL ){
            ListNode* next = cur->next;
            cur->next = pre;
            pre = cur;
            cur = next;
        }

        return pre;
    }
};

92. Reverse Linked List II

反轉一個連結串列從m到n的元素。

  • 如對於連結串列 1->2->3->4->5->NULL, m = 2 , n = 4
  • 則返回連結串列 1->4->3->2->5->NULL
    • m和n超過連結串列範圍怎麼辦?
    • m > n 怎麼辦?

翻轉m到n的元素。

測試連結串列程式。

寫根據陣列建立連結串列和列印連結串列兩個函式

/// LinkedList Test Helper Functions
ListNode* createLinkedList(int arr[], int n){

    if( n == 0 )
        return NULL;

    ListNode* head = new ListNode(arr[0]);
    ListNode* curNode = head;
    for( int i = 1 ; i < n ; i ++ ){
        curNode->next = new ListNode(arr[i]);
        curNode = curNode->next;
    }

    return head;
}

void printLinkedList(ListNode* head){

    ListNode* curNode = head;
    while( curNode != NULL ){
        cout << curNode->val << " -> ";
        curNode = curNode->next;
    }

    cout<<"NULL"<<endl;

    return;
}

void deleteLinkedList(ListNode* head){

    ListNode* curNode = head;
    while( curNode != NULL ){
        ListNode* delNode = curNode;
        curNode = curNode->next;
        delete delNode;
    }

    return;
}

main.cpp:

int main(){

    int arr[] = {1, 2, 3, 4, 5};
    int n = sizeof(arr)/sizeof(int);

    ListNode* head = createLinkedList(arr, n);
    printLinkedList(head);

    ListNode* head2 = Solution().reverseList(head);
    printLinkedList(head2);

    deleteLinkedList(head2);

    return 0;
}

執行結果

83. Remove Duplicates from Sorted List

給出一個有序連結串列,刪除其中所有重複元素,使得每個元素只保留一次。

  • 如 1->1->2,返回1->2
  • 如 1->1->2->3->3,返回1->2->3

86. Partition List

給出一個連結串列以及一個數x,將連結串列重新整理,使得小於x的元素在前;大於等於x的元素在後。

  • 如 1->4->3->2->5->2,x=3
  • 返回 1->2->2->4->3->5

328. Odd Even Linked List

給出一個連結串列,將連結串列重新整理,使得所有索引為奇數的節點排在索引為偶數的節點前面。

  • 如 1->2->3->4->5->NULL
  • 返回 1->3->5->2->4->NULL
  • 第一個節點的索引為1
  • 奇數索引的節點和偶數索引的節點在重新整理後要保持相對順序。

2. Add Two Numbers

給出兩個非空連結串列,表示兩個非負整數。其中每一個整數的各位數字以逆序儲存,返回這兩個整數相加所代表的連結串列。

  • 如 342 + 465 = 807
  • 則給出 2->4->3 和 5->6->4,返回7->0->8

數字中是否有前置的0。(除0以外,沒有前置0)
負數?

445. Add Two Numbers II

給出兩個非空連結串列,表示兩個非負整數。其中每一個整數的各位數字以順序儲存,返回這兩個整數相加所代表的連結串列。

  • 如 342 + 465 = 807
  • 則給出 3->4->2 和 4->6->5,返回8->0->7

如果不允許修改輸入的連結串列呢?

使用輔助資料結構

設立連結串列的虛擬頭結點

203. Remove Linked List Elements

在連結串列中刪除值為val的所有節點

  • 如 1->2->6->3->4->5->6->NULL,要求刪除值為6的節點
  • 返回 1->2->3->4->5->NULL

刪除節點

該邏輯對刪除最後一個元素依然適用

該邏輯對刪除第一個元素不適用

// 不使用虛擬頭結點
class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {

        while( head != NULL && head->val == val ){

            ListNode* node = head;
            head = head->next;
            delete node;
        }

        if( head == NULL )
            return head;

        ListNode* cur = head;
        while( cur->next != NULL ){

            if( cur->next->val == val ){
                ListNode* delNode = cur->next;
                cur->next = delNode->next;
                delete delNode;
                //delNode -> next = NULL;
            }
            else
                cur = cur->next;
        }

        return head;
    }
};

虛擬頭結點

具體實現:

// 使用虛擬頭結點
class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {

        ListNode* dummyHead = new ListNode(0);
        dummyHead->next = head;

        ListNode* cur = dummyHead;
        while( cur->next != NULL ){

            if( cur->next->val == val ){
                ListNode* delNode = cur->next;
                cur->next = delNode->next;
                delete delNode;
            }
            else
                cur = cur->next;
        }

        ListNode* retNode = dummyHead->next;
        delete dummyHead;

        return retNode;
    }
};

int main() {

    int arr[] = {1, 2, 6, 3, 4, 5, 6};
    int n = sizeof(arr)/sizeof(int);

    ListNode* head = createLinkedList(arr, n);
    printLinkedList(head);

    Solution().removeElements( head, 6);
    printLinkedList(head);

    deleteLinkedList( head );

    return 0;
}

82. Remove Duplicates from Sorted List II

給定一個有序連結串列,將其中有重複的元素全部刪除。

  • 如1->2->3->3->4->4->5,返回1->2->5
  • 如1->1->1->2->3,返回2->3

21. Merge Two Sorted Lists

merge兩個有序的連結串列

複雜的連結串列操作

24. Swap Nodes in Pairs

給定一個連結串列,對於每兩個相鄰的節點,交換其位置。

  • 如:連結串列為 1->2->3->4->NULL
  • 返回:2->1->4->3->NULL
  • 只能對節點進行操作,不能修改節點的值

虛擬頭結點

虛擬頭結點

next指標調整

核心在於建立幾個指標預先保留。

class Solution {
public:
    ListNode* swapPairs(ListNode* head) {

        ListNode* dummyHead = new ListNode(0);
        dummyHead->next = head;

        ListNode* p = dummyHead;
        while( p->next && p->next->next ){
            ListNode* node1 = p->next;
            ListNode* node2 = node1->next;
            ListNode* next = node2->next;
            node2->next = node1;
            node1->next = next;
            p->next = node2;
            p = node1;
        }

        ListNode* retHead = dummyHead->next;
        delete dummyHead;

        return retHead;
    }
};

int main() {

    int arr[] = {1, 2, 3, 4};
    int n = sizeof(arr)/sizeof(int);

    ListNode* head = createLinkedList(arr, n);
    printLinkedList(head);

    head = Solution().swapPairs( head );
    printLinkedList(head);

    deleteLinkedList(head);

    return 0;
}

思考:可不可以不用next指標?

25. Reverse Nodes in k-Group

給定一個連結串列,每k個節點為一組,反轉每一組的k個節點。k為正整數且小於等於連結串列長度。如果連結串列長度不是k的整數倍,剩餘部分不需要進行反轉。如: 1->2->3->4->5->NULL

  • 若 k = 2,則結果為:2->1->4->3->5->NULL
  • 若 k = 3,則結果為:3->2->1->4->5->NULL

147. Insertion Sort List

為一個連結串列進行插入排序

148. Sort List

寫一個排序演算法,用O(nlogn)的時間複雜度為一個連結串列進行排序

歸併排序不需要使用陣列索引:自底向上。

不僅僅是穿針引線

237. Delete Node in a Linked List

給定連結串列中的一個節點,刪除該節點

class Solution {
public:
    void deleteNode(ListNode* node) {
        
    }
};

3賦值上4,然後把4的next儲存。讓第一個4指向第二個四的next

3賦值上4,然後把4的next儲存。讓第一個4指向第二個四的next

class Solution {
public:
    void deleteNode(ListNode* node) {

        assert(node != NULL && node->next != NULL);

        node->val = node->next->val;
        ListNode* delNode = node->next;
        node->next = delNode->next;

        delete delNode;

        return;
    }
};

特殊情況改變節點的值來實現我們需要的功能。

雙指標技術

19. Remove Nth Node From End of List

給定一個連結串列,刪除倒數第n個節點

  • 如:1->2->3->4->5->NULL, n = 2
  • 返回:1->2->3->5

n從0計還是從1計
n不合法,負數或者大於連結串列長度如何處理(保證n合法)

解法1:先遍歷一遍計算連結串列長度;再遍歷一遍刪除倒數第n個節點
遍歷兩遍連結串列。能否只遍歷一遍連結串列?

pq長度固定

具體實現:

// 先記錄連結串列總長度
// 需要對連結串列進行兩邊遍歷
class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {

        ListNode* dummyHead = new ListNode(0);
        dummyHead->next = head;

        int length = 0;
        for(ListNode* cur = dummyHead->next ; cur != NULL ; cur = cur->next )
            length ++;
        //cout<<length<<endl;

        int k = length - n;
        assert( k >= 0 );
        ListNode* cur = dummyHead;
        for( int i = 0 ; i < k ; i ++ )
            cur = cur -> next;

        ListNode* delNode = cur->next;
        cur->next = delNode->next;
        delete delNode;

        ListNode* retNode = dummyHead->next;
        delete dummyHead;
        return retNode;
    }
};

使用雙指標:

// 使用雙指標, 對連結串列只遍歷了一遍
class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {

        ListNode* dummyHead = new ListNode(0);
        dummyHead->next = head;

        ListNode* p = dummyHead;
        ListNode* q = dummyHead;
        for( int i = 0 ; i < n + 1 ; i ++ ){
            assert(q);
            q = q->next;
        }

        while( q ){
            p = p->next;
            q = q->next;
        }

        ListNode* delNode = p->next;
        p->next = delNode->next;
        delete delNode;

        ListNode* retNode = dummyHead->next;
        delete dummyHead;

        return retNode;
    }
};

61. Rotate List

給定一個連結串列,讓這個連結串列向右旋轉k位。其中k為非負數。

  • 如:1->2->3->4->5->NULL, k = 2
  • 第一次旋轉:5->1->2->3->4->NULL
  • 第二次旋轉:4->5->1->2->3->NULL

143. Reorder List

給定一個連結串列 L(0) -> L(1) -> L(2) -> … -> L(n-1) -> L(n)
將其變為 L(0) -> L(n) -> L(1) -> L(n-1) -> L(2) -> L(n-2)…
的形式

  • 連結串列無法隨機訪問資料,如何獲得中間的元素?
  • 兩次遍歷?一次遍歷?

234. Palindrome Linked List

給一個連結串列,判斷這個連結串列是否為迴文(正看反看)連結串列。

  • 能否使用O(1)的空間複雜度解決問題?



作者:天涯明月笙
連結:https://www.jianshu.com/p/98aac3b8c12a