文章链接:链表基础知识203.移除链表元素707.设计链表206.反转链表

视频链接:203.移除链表元素707.设计链表206.反转链表

题目链接:203.移除链表元素707.设计链表206.反转链表

链表学习

今天是第一次正式的学习链表代码的书写,之前大一上学期去图书馆看过有关“数据结构”的书籍,但没有看用C++代码写的“数据结构”,所以可以说今天的对链表的学习是从小白开始的。

以下是根据代码随想录所作的简单思维导图:

代码随想录算法训练营第三天| 203.移除链表元素 、707.设计链表、206.反转链表_链表

清晰版本(幕布网址):https://mubucm.com/doc/1GVSbM1VVCu

看完答案后的想法

203.移除链表元素

移除链表元素有两种办法:一、直接使用原来的链表进行移除节点操作;二、设置一个虚拟头节点再进行移除节点操作

// 方法一:
class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        
        // 删除头节点
        while(head != NULL && head->val == val){
        // 头节点不为空且数据域的元素为目标元素
        // 注意不是if

            ListNode* tmp = head;  
            head = head->next; // 更新头节点:头节点指向原头节点的下一个数据的地址
            delete tmp; // 删除原头节点,释放内存
        }


        // 删除非头节点
        ListNode* cur = head; // cur为head别名
        while(cur != NULL && cur->next != NULL){
        // 头节点不为空且其下一个节点不为空
        // 注意不是if

            if(cur->next->val == val){
                ListNode* tmp = cur->next;
                cur->next = cur->next->next;
                delete tmp; // 释放内存
            }
            else{
                cur = cur->next; // 更新cur
            }
        }
        return head; // 返回头节点
    }
};
// 方法二:
class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        ListNode* dummyHead = new ListNode(0); // 设置一个虚拟头节点
        // new(): 分配这种类型的一个大小的内存空间,并以括号中的值来初始化这个变量;

        dummyHead->next = head; // 虚拟头节点指向head
        ListNode* cur = dummyHead; // 定义一个临时指针进行遍历链表

        while(cur->next != NULL){
            if(cur->next->val == val){
                ListNode* tmp = cur->next;
                cur->next = cur->next->next;
                delete tmp;
            }
            else{
                cur = cur->next;
            }
        }
        head = dummyHead->next;
        delete dummyHead; // 记得释放内存
        return head; // 返回头节点
    }
};

707.设计链表

这道题是自己实现一个链表,其中包括五个函数:

零、定义链表节点结构体/初始化链表

// 定义链表节点结构体
    struct LinkedNode{
        int val;
        LinkedNode* next;
        LinkedNode(int val):val(val), next(nullptr){}
    }; // 别忘了分号

    // 初始化链表
    MyLinkedList() {
        dummyhead = new LinkedNode(0); //定义一个虚拟头节点
        l_size = 0;
    }

一、获取指定节点的值

// 获得指定节点的值 
    int get(int index) {
        if(index > (l_size - 1) || index < 0){ // 下标从0开始
            return -1;
        }
        LinkedNode* cur = dummyhead->next;
        while(index--){ // 不能写--index
            cur = cur->next;
        }
        return cur->val;
    }

二、头部插入节点

// 头部插入节点
    void addAtHead(int val) {
        LinkedNode* newNode = new LinkedNode(val);
        newNode->next = dummyhead->next; // 两个不能互换位置
        dummyhead->next = newNode;
        l_size++;
    }

三、尾部插入节点

// 尾部插入节点
    void addAtTail(int val) {
        LinkedNode* newNode = new LinkedNode(val);

        // 查询操作:找链表原先的最后一个元素
        LinkedNode* cur = dummyhead;
        while(cur->next != nullptr){
            cur = cur->next;
        }
        cur->next = newNode; // 不用再将newNode的指针域指向NULL,因为本来就指向NULL
        l_size++;
    }

四、指定节点前插入节点

// 指定节点前插入节点
    void addAtIndex(int index, int val) {
        if(index > l_size) return;
        if(index < 0) index = 0; // index小于0,则在头部插入节点
        LinkedNode* newNode = new LinkedNode(val);
        LinkedNode* cur = dummyhead;
        while(index--){
            cur = cur->next;
        }
        newNode->next = cur->next;
        cur->next = newNode;
        l_size++;
    }

五、删除指定节点

// 删除指定节点
    void deleteAtIndex(int index) {
        if(index > (l_size - 1) || index < 0){
            return;
        }
        LinkedNode* cur = dummyhead;
        while(index--){
            cur = cur->next; // 第n个节点是cur->next
        }
        LinkedNode* tmp = cur->next; // 用于释放内存
        cur->next = cur->next->next;
        delete tmp;
        tmp = NULL; // 避免tmp成为野指针
        l_size--;
    }

最后、打印链表

// 打印链表
    void printLinkedList(){
        LinkedNode* cur = dummyhead;
        while(cur->next != NULL){
            cout << cur->next->val << " ";
            cur = cur->next;
        }
        cout << '\n';
    }

完整代码:

class MyLinkedList {
public:
    // 定义链表节点结构体
    struct LinkedNode{
        int val;
        LinkedNode* next;
        LinkedNode(int val):val(val), next(nullptr){}
    }; // 别忘了分号

    // 初始化链表
    MyLinkedList() {
        dummyhead = new LinkedNode(0); //定义一个虚拟头节点
        l_size = 0;
    }
    
    // 获得指定节点的值 
    int get(int index) {
        if(index > (l_size - 1) || index < 0){ // 下标从0开始
            return -1;
        }
        LinkedNode* cur = dummyhead->next;
        while(index--){ // 不能写--index
            cur = cur->next;
        }
        return cur->val;
    }
    
    // 头部插入节点
    void addAtHead(int val) {
        LinkedNode* newNode = new LinkedNode(val);
        newNode->next = dummyhead->next; // 两个不能互换位置
        dummyhead->next = newNode;
        l_size++;
    }
    
    // 尾部插入节点
    void addAtTail(int val) {
        LinkedNode* newNode = new LinkedNode(val);

        // 查询操作:找链表原先的最后一个元素
        LinkedNode* cur = dummyhead;
        while(cur->next != nullptr){
            cur = cur->next;
        }
        cur->next = newNode; // 不用再将newNode的指针域指向NULL,因为本来就指向NULL
        l_size++;
    }
    
    // 指定节点前插入节点
    void addAtIndex(int index, int val) {
        if(index > l_size) return;
        if(index < 0) index = 0; // index小于0,则在头部插入节点
        LinkedNode* newNode = new LinkedNode(val);
        LinkedNode* cur = dummyhead;
        while(index--){
            cur = cur->next;
        }
        newNode->next = cur->next;
        cur->next = newNode;
        l_size++;
    }
    
    // 删除指定节点
    void deleteAtIndex(int index) {
        if(index > (l_size - 1) || index < 0){
            return;
        }
        LinkedNode* cur = dummyhead;
        while(index--){
            cur = cur->next; // 第n个节点是cur->next
        }
        LinkedNode* tmp = cur->next; // 用于释放内存
        cur->next = cur->next->next;
        delete tmp;
        tmp = NULL; // 避免tmp成为野指针
        l_size--;
    }

    // 打印链表
    void printLinkedList(){
        LinkedNode* cur = dummyhead;
        while(cur->next != NULL){
            cout << cur->next->val << " ";
            cur = cur->next;
        }
        cout << '\n';
    }

private:
    int l_size;
    LinkedNode* dummyhead;
};

/**
 * Your MyLinkedList object will be instantiated and called as such:
 * MyLinkedList* obj = new MyLinkedList();
 * int param_1 = obj->get(index);
 * obj->addAtHead(val);
 * obj->addAtTail(val);
 * obj->addAtIndex(index,val);
 * obj->deleteAtIndex(index);
 */

206.反转链表

两种方法:双指针法 和 递归法

双指针法

// 双指针法
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        // 临时指针tmp用于保存cur的下一个节点
        ListNode* temp;

        // 初始化
        ListNode* cur = head;
        ListNode* pre = NULL;
        while(cur){ // cur指向NULL时终止循环
            temp = cur->next;
            cur->next = pre;
            pre = cur;
            cur = temp;
        } 
        return pre;
    }
};

递归法

// 递归法
class Solution {
public:
    ListNode* reverse(ListNode* pre, ListNode* cur){ // 注意函数定义(返回的时指针)
        if(cur == NULL) return pre;
        ListNode* temp = cur->next;
        cur->next = pre;
        return reverse(cur, temp);
    }

    ListNode* reverseList(ListNode* head) {
         return reverse(NULL, head);
    }
};

遇到困难

203.移除链表元素

为什么方法二(设置一个虚拟头节点)定义完虚拟头节点后要再定义临时指针(cur)进行遍历?

解答:因为如果用头指针进行遍历,那么头指针是一直在改变的;而用临时指针时,头指针是不会改变的。

707.设计链表

定义结构体时最后少了分号,卡了好久……好傻。

注意下标从哪里开始。

刚开始不知道为什么在deleteAtIndex()函数中要设立一个临时指针tmp,最后知道是为了释放内存。

206.反转链表

双指针法中,因为cur->next会改变,所以要用到临时指针temp用于保存cur->next。

递归法中, 对函数的定义不太熟悉。

今日收获

今天用4个小时深入学习了链表的知识,知道了链表的定义及其相关操作。