【算法篇】鏈表專題


  前言:

  從今天開始要攻克算法專題了,今天是鏈表篇,關於鏈表相關的考題,不會太多涉及時間復雜度,而主要考察鏈表和指針操作;為啥大廠喜歡考察數據結構和算法?因為這些是對基本功的升華,不會考察數組指針、函數指針等,考一個鏈表,就能考察對指針的理解,我相信不理解指針,鏈表學起來很費勁!

  一、簡介

  我會一個模塊一個模塊進行學習和練習,練習時我會從leetcode上選題,都知道leetcode吧?是OJ中最權威的平台了,在上面可以找算法題和練習,很好的一個網站,每一個題都會說明leetcode的第幾題,方便大家查找和練習。

  二、反轉鏈表

  LeetCode上第206題:Reverse Linked List,官網是英文,但鑒於英文對一些人看起來比較費勁,翻譯成中文,如下:

反轉單鏈表。
例子:
輸入:1 - > 2 - > 3 - > 4 - > 5 - > NULL
輸出:5 - > 4 - > 3 - > 2 - > 1 - > NULL
跟進:
鏈表可以迭代或遞歸地反轉。你能實現這兩個嗎?

  可能有人會想到直接去改鏈表節點里的值,這是不允許,一般都是操作next指針,去改變指針指向;畫圖進行講解,如下:

   

  需要三個指針pre/cur/next去反轉,將2位置指向pre位置,pre指向1號位置,1號位置指向3號位置,這樣就可以進行反轉了。代碼如下:

struct ListNode {
    int val;
    ListNode *next;
    ListNode(int x) : val(x), next(NULL) {}
};

// 206. Reverse Linked List
// https://leetcode.com/problems/reverse-linked-list/description/
// 時間復雜度: 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;
    }
};

  我是按照LeetCode的格式進行編寫的,然后去LeetCode上去試一下,鑒於可能有人不知道怎么使用LeetCode,我簡單進行演示一下怎么使用:

  第一步:百度leetcode,如下:

  

  

    第二步:點擊“Create Account”,創建自己的用戶,需要填寫郵箱,需要點擊鏈接進行激活,否則刷題無法提交;剛開始寫的郵箱沒給我發郵件,又在個人資料里重新換了郵箱,就可以收到了;

    第三步:在首頁找題,如果能記住題目,可以輸入題目進行搜索;也可以搜索題號,如206,也可以搜索到,如下圖:

 

  

第四步:提交代碼,我將上面寫的代碼放到leetcode,點擊右下角的“Submit Solution”,就可以看到下面的“Submission Solution:Accepted”,就表示通過了,如下圖:

  

   這樣就OK了。

  三、測試程序

  這部分主要說明一下怎么去自己測試程序的運行?主要實現鏈表的創建、遍歷、銷毀(C++堆上內存要自己管理)。

  1、創建鏈表

  將數組傳給函數,根據數組實現鏈表賦值;還會傳入n創建多大的鏈表,代碼如下:

// 根據n個元素的數組arr創建一個鏈表, 並返回鏈表的頭
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;
}

  注意:創建的鏈表,沒有真實的“頭結點”,就是只存一個開始指針的節點,所以刪除第一個節點要注意!

  2、遍歷鏈表

  通過頭結點進行遍歷鏈表,代碼如下:

// 打印以head為頭結點的鏈表信息內容
void printLinkedList(ListNode* head){

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

    cout << "NULL" << endl;

    return;
}

  3、銷毀鏈表

  將創建時分配的內存釋放,代碼如下:

// 釋放以head為頭結點的鏈表空間
void deleteLinkedList(ListNode* head){

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

    return;
}

  4、測試程序

  對反轉鏈表代碼進行測試,整體代碼如下:

#include <iostream>

using namespace std;

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

/// LinkedList 測試輔助函數

// 根據n個元素的數組arr創建一個鏈表, 並返回鏈表的頭
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;
}

// 打印以head為頭結點的鏈表信息內容
void printLinkedList(ListNode* head){

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

    cout << "NULL" << endl;

    return;
}

// 釋放以head為頭結點的鏈表空間
void deleteLinkedList(ListNode* head){

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

    return;
}


// 206. Reverse Linked List
// https://leetcode.com/problems/reverse-linked-list/description/
// 時間復雜度: 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;
    }
};

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;
}
View Code

  運行結果如下:

  

  進行了反轉,沒有問題;

  四、刪除鏈表元素

   1、題目

  LeetCode上第203題目:Remove Linked List Elements,題目如下:

從具有值val的整數鏈表中刪除所有元素。
例子:
輸入:1->2->6->3->4->5->6,val = 6
輸出:1 - > 2 - > 3 - > 4 - > 5

  2、分析題目

  先來分析一下題目,用圖來解釋如下:

  

  假如刪除值為4的節點,先把4的next指針保存,在3號位置指向5,這完全沒有問題;但問題會發生在第一個節點位置,它沒有前一個節點,那怎么辦呢?在前面創建鏈表時也說過:沒有頭結點,所以使用虛擬頭結點

  代碼如下:

// 203. Remove Linked List Elements
// https://leetcode.com/problems/remove-linked-list-elements/description/
// 使用虛擬頭結點
// 時間復雜度: O(n)
// 空間復雜度: O(1)
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;
    }
};

  3、測試

  測試程序也是上面的鏈表創建和遍歷,程序如下:

 

#include <iostream>

using namespace std;

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

/// 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){

    if(head == NULL){
        cout << "NULL" << endl;
        return;
    }

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

    cout << endl;

    return;
}

void deleteLinkedList(ListNode* head){

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

    return;
}

// 203. Remove Linked List Elements
// https://leetcode.com/problems/remove-linked-list-elements/description/
// 使用虛擬頭結點
// 時間復雜度: O(n)
// 空間復雜度: O(1)
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;
}
View Code

  運行結果如下:

  

   總結:

  希望通過這篇博客,大家能對基本的鏈表算法題能輕松應對;歡迎點贊,不懂的歡迎隨時評論!多多支持,謝謝!

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM