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

1-学习的文章和视频链接

1.1 203移除链表元素-代码随想录
1.2 203移除链表元素-卡哥B站视频讲解
1.3 707设计链表-代码随想录
1.4 707设计链表-卡哥B站视频讲解
1.5 206反转链表-代码随想录
1.6 206反转链表-卡哥B站视频讲解


2-题目

203.移除链表元素

<1> 使用原链表

class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        // 删除头节点
        // 如果链表中,从头结点开始每个节点的值都等于val,
        // 删除头节点即可,这也是使用while不使用if的原因
        
        // 链表不能为空 且 头节点的值等于val
        while(head!=NULL && head->val==val){
            ListNode *tmp = head;    // 临时节点等于头节点,用来存储等于val的节点
            head = head->next;       // 移动:头节点等于当前头节点的下一个节点
            delete tmp;              // 删除临时节点
        }

        // 删除非头节点
        ListNode *cur = head;         // 现节点等于头节点,用来移动
        // 链表不能为空 且 现节点的下一节点不能指向空
        while(cur!=NULL && cur->next!=NULL){
            if(cur->next->val==val){  // 现节点的下一节点的值等于val
                // 临时节点等于现节点,用于存储等于val的现节点的下一节点
                ListNode *tmp = cur->next;
                cur->next = cur->next->next;  // 现节点的下一节点等于现节点的下下节点
                delete tmp;
            }
            else // 现节点的下一节点的值与val不等
                cur = cur->next;       // 现节点等于下一节点
        }
        return head;     // 返回链表
    }
};

<2> 使用虚拟头节点

class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        // 定义一个虚拟头节点,就不用考虑头节点和非头节点的状况了
        ListNode *dummyNode = new ListNode(0);
        dummyNode->next = head;               // 虚拟头节点指向原头节点,表示新链表
        ListNode *cur = dummyNode;            // 定义现节点,等于虚拟节点,用来滑动
        
        // 现节点的下一节点不为空
        while(cur->next!=NULL){
            if(cur->next->val==val){           // 当现节点的下一节点的值等于val
                // 定义一个临时节点等于现节点的下一节点,存储相等值的节点
                ListNode *tmp = cur->next;
                cur->next = cur->next->next;   // 连接到下下节点
                delete tmp;                    // 删除临时节点
            }
            else{                              // 值不相等,跳过
                cur = cur->next;               // 滑动到下一节点
            }
        }
        head = dummyNode->next;                // 返回新链表
        delete dummyNode;                      // 删除虚拟节点
        return head;                           // 返回链表
    }
};
  • 思路:分为使用原链表和虚拟头节点两种方法,主要是记得定义临时节点和现节点,临时节点保存值相等的节点,现节点用来滑动,指向值相等节点的上一个节点。
  • 实现难点:使用原链表时,删除头节点的循环容易写成条件;现节点容易指错,应该指向变动的上一个节点;别忘记在 循环条件中写上链表非空且下一节点非空。
  • 收获:删除元素也不容易,临时节点和现节点的定义很重要。

707.设计链表

class MyLinkedList {
public:
    // 定义单链表结构体
    struct LinkedNode{
        int val;              // 节点的值
        LinkedNode* next;     // 节点指针
        LinkedNode(int val): val(val), next(nullptr){}
    };

    // 初始化单链表
    MyLinkedList() {
        _dummyHead = new LinkedNode(0);  // 初始化虚拟节点,不是真头节点
        _size = 0;                       // 初始化链表长度
    }
    
    // 获取第index个节点的值
    int get(int index) {
        // 判断索引合法与否
        if(index>=_size || index<0){
            return -1;
        }
        // 用要取值的节点作为现节点即可,因为无需考虑指针变换操作
        LinkedNode* cur = _dummyHead->next;  // 滑动节点
        while(index--){                      // 先判断index,再减
            cur = cur->next;                 // 一个节点一个节点移动
        }
        return cur->val;                     // 返回现节点的值
    }
    
    // 插入新节点为头节点
    void addAtHead(int val) {
        LinkedNode* newHead = new LinkedNode(val);  // 定义新节点
        // 新节点指向虚拟头节点的下一节点
        newHead->next = _dummyHead->next;
        // 虚拟头节点指向新节点,新节点成为头节点
        _dummyHead->next = newHead;
        _size++;                           // 记得插入后长度加1
    }
    
    // 插入新节点为尾节点
    void addAtTail(int val) {
        LinkedNode* newTail = new LinkedNode(val);  // 定义新节点
        LinkedNode* cur = _dummyHead;               // 定义现节点
        while(cur->next!=nullptr){      // 循环当现节点的下一节点不为空
            cur = cur->next;            // 滑动现节点至下一个节点
        }
        cur->next = newTail;  // 循环到现节点为最后一个节点,插入新节点
        _size++;              // 链表长度加1
    }
    
    // 在第index个节点前添加值为val的新节点
    void addAtIndex(int index, int val) {
        if(index>_size){
            return;
        }
        if(index<0){
            index=0;
        }
        LinkedNode* newNode = new LinkedNode(val);   // 定义新节点
        LinkedNode* cur = _dummyHead;                // 定义现节点
        // 遍历现节点至index
        while(index--){
            cur = cur->next;
        }
        newNode->next = cur->next;   // 新节点指向现节点的下一节点
        cur->next = newNode;         // 现节点指向新节点
        _size++;                     // 链表长度加1
    }
    
    // 删除第index个节点
    void deleteAtIndex(int index) {
        if(index>=_size || index<0){      // index合法性检查
            return;
        }
        LinkedNode* cur = _dummyHead;     // 定义现节点
        // 滑动节点至index
        while(index--){
            cur = cur->next;
        }
        LinkedNode* tmp = new LinkedNode(0);   // 定义临时节点
        tmp = cur->next;               // 临时节点存储现节点的下一节点
        // 断开现节点和下一节点,而和下下节点连接
        cur->next = cur->next->next;
        delete tmp;                   // 删除临时节点
        _size--;                      // 链表长度减1
    }

    // 打印链表
    void printLinkedNode(){
        LinkedNode* cur = _dummyHead;        // 定义现节点
        while(cur->next!=nullptr){           // 当现节点的下一节点不为空
            cout << cur->next->val << " "; // 输出现节点的下一节点和空格
            cur = cur->next;                 // 滑动到下一个
        }
        cout << endl;
    }

private:
    int _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);
 */
  • 思路:首先要定义链表的结构,有其中值和指向下一个节点的指针,两部分组成。获取第index节点的值,每个节点判断时候为index,然后输出。插入新节点在头位置时,新节点指针先指向原头节点,然后把虚拟头节点指向新节点。插入新节点为尾节点时,滑动不是index判断,而是现节点的下一节点的指针是否指向空。在第index个节点前插入新节点时,判断index的合法性,滑动每个现节点直到找到第index节点,新节点指向下一节点,现节点指向新节点。在删除第index个节点时,首先也要判断index的合法性,找到第index个节点,要定义临时节点去储存要删除的节点,下节点指向下下节点,最后要删除临时节点。
  • 实现难点:定义链表长度和虚拟头节点会暴露在初始化;添加或删除节点时,最后一定要变更链表长度;记得现节点指向的是哪个节点。
  • 收获:熟练使用节点的遍历;清楚知道了滑动的现节点应该指向前节点还是本身;熟悉了结构体定义;删除或添加节点的时候,链表长度记得变更。

206.反转链表

<1> 双指针法

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode* tmp;              // 定义临时节点
        ListNode* pre = nullptr;    // 定义一个前节点,以便节点反转指向这个前节点
        ListNode* cur = head;       // 定义现节点,从头节开始依次反转
        while(cur){                 // cur直到指向空,退出循
            tmp = cur->next;        // 临时节点保存现节点的原下一节点,因为反转后会丢失连接
            cur->next = pre;        // 现节点指向定义的前节点
            pre = cur;              // 前节点移动到现节点位置(向原链表方向移动一位)
            cur = tmp;              // 现节点移动到临时节点位置(向原链表方向移动一位)
        }
        delete tmp;                 // 删除临时节点
        return pre;                 // 打印反向链表,注意现节点已经指向空,不能作为头节点表示
    }
};

<2> 递归法(双指针思路)

class Solution {
public:
    ListNode* reverse(ListNode* cur, ListNode* pre){
        if(cur==nullptr) return pre;        // 如果现节点指向空,则返回新链表, 新头节点pre
        ListNode* tmp = cur->next;          // 定义临时节点,保存现节点的下一节点
        cur->next = pre;                    // 反向操作,现节点指向前节点
        // 递归,前节点先移动到现节点,现节点再移动到临时节点
        // pre = cur;
        // cur = tmp;
        return reverse(tmp, cur);
    }

    ListNode* reverseList(ListNode* head) {
        // 递归,初始化,现节点指向头节点,前节点指向空
        // cur = head;
        // pre = nullptr;
        return reverse(head, nullptr);
    }
};

<3> 递归法(从后往前翻转)

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        // 边界条件判断,当链表未空或链表中只有1个节点时,返回的都是链表
        if(head==nullptr || head->next==nullptr) return head;
        ListNode* last = reverseList(head->next);   // 递归,从第二个节点开始依次反转
        head->next->next = head;                    // 反转指向
        head->next = nullptr;                       // 原头指为空
        return last;                                // 返回原链表的尾节点
    }
};
  • 思路:双指针法直接定义一前一后2个节点,还要再定义一个临时节点保存后一节点反向时,下一节点丢失的位置,不断移动前后节点,实现反向。用双指针思想的递归方法中,判断cur和pre的位置。从后往前翻转有点难理解,当就是依次链表反转。
  • 实现难点:双指针法中pre和cur节点谁先移动,是有顺序的;双指针思想的递归方法中,cur和pre初始化的值,递归过程中的值,顺序不要搞错;
  • 收获:递归方法很快但是双指针方法更清晰。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值