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

一、LeeCode 203 移除链表元素


 题目链接:203. 移除链表元素

 文档讲解:来自代码随想录

 视频讲解:卡子哥视频讲解系列——移除链表元素


思路:我采用虚拟头结点法(图片来自代码随想录

(如果没有虚拟头结点,那么在该链表中,删除非头结点和删除头结点的操作会不同)

设置一个虚拟头结点,那么包括原头结点在内,每个节点都有一个前节点和一个后节点,移除元素的操作可以按照同一的方式进行。我在代码中给出了尽可能详细的注释。

class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        // 创建虚拟头结点
        ListNode* dummyHead = new ListNode(0);
        // 使虚拟头结点指向原头结点
        dummyHead->next = head;
        // 创建cur指针从虚拟头结点依次向后指,用于对链表进行操作
        ListNode* cur = dummyHead;
        // 如果要操作一个节点,则必须让cur指向该节点的前节点,这样才能用cur->next来操作该节点
        // 要操作的节点肯定不能为空,因此循环条件为 cur->next != NULL
        while (cur->next != NULL) {
            // 发现目标值时,进行删除操作
            if (cur->next->val == val) {
                // 用tmp暂时存放要移除的此节点,用于一会释放内存的操作
                ListNode* tmp = cur->next;
                // 此节点为cur->next, 前节点为cur, 后节点为cur->next->next
                // 让前节点直接指向后节点,就把此节点从链表中移除了
                cur->next = cur->next->next;
                // 释放移除的节点的内存
                delete tmp;
            } else {
                // 让cur移向下一个节点,再做一遍以上操作
                cur = cur->next;
            }
        }
        // 新链表的头结点为虚拟节点的下一个节点
        head = dummyHead->next;
        // 释放用于辅助操作的虚拟头结点
        delete dummyHead;
        // 根据要求,返回新链表的头结点
        return head;
    }
};

二、LeeCode 707 设计链表


 题目链接:707. 设计链表

 文档讲解:来自代码随想录

 视频讲解:卡子哥视频讲解系列——设计链表


这道题目设计链表的五个接口:

  • 获取链表第index个节点的数值
  • 在链表的最前面插入一个节点
  • 在链表的最后面插入一个节点
  • 在链表第index个节点前面插入一个节点
  • 删除链表的第index个节点

思路:仍采用虚拟头节点的方法,在代码中给出我理解的尽可能详细的注释

class MyLinkedList {
public:
    // 定义链表节点结构体
    struct LinkedNode {
        int val;
        LinkedNode* next;
        LinkedNode(int val):val(val), next(nullptr) {}  // 在声明时会初始化节点结构体
    };

    // 初始化链表
    MyLinkedList() {
        _dummyHead = new LinkedNode(0);     // 定义虚拟头结点
        _size = 0;    // 初始链表长度为 0
    }
    
    // 获取链表中下标为 index 的节点的值。如果下标无效,则返回 -1
    int get(int index) {
        if (index < 0 || index > (_size - 1)) {
            return -1;
        }
        // 创建指针cur指向头结点
        LinkedNode* cur = _dummyHead->next;
        // 遍历到下标为 index 的节点
        while (index--) {
            cur = cur->next;
        }
        // 返回下标为 index 的节点值
        return cur->val;
    }
    
    // 将一个值为 val 的节点插入到链表中第一个元素之前。在插入完成后,新节点会成为链表的第一个节点
    void addAtHead(int val) {
        LinkedNode* newNode = new LinkedNode(val);  // 创建要插入的节点
        LinkedNode* cur = _dummyHead;
        // 此时cur指向虚拟头结点,cur->next表示头结点
        // 让新插入的节点指向头结点,那么插入的节点就变成了新的头结点
        newNode->next = cur->next;
        // 再让虚拟头结点指向新的头节点,完成插入
        cur->next = newNode;
        // 链表长度+1
        _size++;
    }
    
    // 将一个值为 val 的节点追加到链表中作为链表的最后一个元素
    void addAtTail(int val) {
        LinkedNode* newNode = new LinkedNode(val);
        // 创建指针cur指向虚拟头结点
        LinkedNode* cur = _dummyHead;
        // 遍历到链表尾部
        while (cur->next != NULL) {
            cur = cur->next;
        }
        // 此时cur指向链表最后一个元素,让cur->next = newNode
        cur->next = newNode;
        // 链表长度+1
        _size++;
    }
    
    // 将一个值为 val 的节点插入到链表中下标为 index 的节点之前
    // 如果 index 等于链表的长度,那么该节点会被追加到链表的末尾
    // 如果 index 比长度更大,该节点将不会插入到链表中
    void addAtIndex(int index, int val) {
        if (index > _size || index < 0) {
            return;
        }
        LinkedNode* newNode = new LinkedNode(val);
        // cur初始指向虚拟头结点
        LinkedNode* cur = _dummyHead;
        // 遍历到cur指向index节点的前一个节点
        // 只有这样才能通过cur->next操作index节点
        while (index--) {
            cur = cur->next;
        }
        // 此时cur指向index节点的前节点
        newNode->next = cur->next;    // 让新节点指向index节点
        cur->next = newNode;          // 让前节点指向新节点,完成插入
        // 链表长度+1
        _size++;
    }
    
    // 如果下标有效,则删除链表中下标为 index 的节点
    void deleteAtIndex(int index) {
        if (index < 0 || index >= _size) {
            return;
        }
        LinkedNode* cur = _dummyHead;
        while (index--) {
            cur = cur->next;
        }
        // 遍历后,cur指向index节点的前节点
        LinkedNode* tmp = cur->next;    // tmp暂存要移除的index节点,方便一会释放
        cur->next = cur->next->next;    // index节点的前节点直接指向后节点
        delete tmp;
        tmp = NULL;    // 释放tmp后防止tmp变成野指针,故赋值空
        _size--;       // 链表长度-1
    }

private:
    // 在类里使用的变量
    int _size;                 // 链表长度
    LinkedNode* _dummyHead;    // 虚拟头结点
};

注意:(1)(链表节点编号从0开始的情况下)如果cur初始指向虚拟头结点,那么while (index--) {cur = cur->next;}遍历后,cur就指向index节点的前节点;如果cur初始指向头结点,那么while (index--) {cur = cur->next;}遍历后,cur就指向index节点;(2)要想操作index节点,必须让指针cur指向index节点的前节点,这样才能用cur->next来操作index节点;

三、LeeCode 206 反转链表


 题目链接:206. 反转链表

 文档讲解:来自代码随想录

 视频讲解:卡子哥视频讲解系列——反转链表


思路:链表是按顺序一个节点的next指针指向下一个节点,想要反转链表,只需要改变链表的next指针的指向,如图所示截取自代码随想录

1. 双指针法

定义指针 cur 初始指向头节点,定义指针 pre 初始指向 NULL

将 cur->next 指向 pre,就达到了翻转的目的

然后 cur 和 pre 依次向下移一位:在翻转前 cur->next 是指向节点1的(从0开始记),用 tmp 先保存 cur->next,用于cur向下移一位,然后再翻转。

(代码中给出我理解的尽可能详细的注释)

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode* cur = head;    // cur初始指向头结点
        ListNode* pre = NULL;    // pre初始指向NULL
        ListNode* tmp;           // 暂存cur的下一个节点
        while (cur != NULL) {
            // 先存cur的下一个节点,否则等到cur->next改变后,就找不到cur的下一个节点了
            tmp = cur->next;
            // 改变cur指向前一位(头节点指向NULL变为尾节点)
            cur->next = pre;
            // cur和pre都向下移一位
            pre = cur;  // 先更新pre
            cur = tmp;  // 再更新cur
        }
        return pre; // 最终是pre指向新链表的头,cur指向NULL
    }
};

2. 递归法

思路:递归法我理解的比较慢,和双指针法是一样的逻辑,当cur为空的时候循环结束,不断将cur指向pre。

class Solution {
public:
    ListNode* reverse(ListNode* pre, ListNode* cur) {
        if (cur == NULL) {
            return pre;
        }
        ListNode* tmp;
        tmp = cur->next;
        cur->next = pre;
        return reverse(cur, tmp);
    }

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值