代码随想录(day3)——链表

本文详细讲解了LeetCode中链表操作的两个问题:移除指定值的节点和反转链表。涉及哨兵位的使用、空链表处理及复杂情况的遍历策略。
摘要由CSDN通过智能技术生成

Leetcode.203 移除链表元素:

203. 移除链表元素 - 力扣(LeetCode)

       对于本题,难点就在于对于头部结点的删除,以及给定链表为空时,如何进行遍历。因为需要遍历链表,假设访问链表下一个结点所对应的代码为head->next,若此时的链表为空,则一旦运行该代码,则直接会被判定越界。造成错误。因此,为了解决上述问题,可以在给定的链表的头结点之前,人工添加一个哨兵位头结点。该链表中的val=0,没有任何实际意义。为了方便表述,文章后面针对哨兵位头结点统一命名为phead。具体结构如下:

在测试用例中,可以看到,题目给了这样的数据:

如果用上面的图形来表示这种链式结构,即:

      对于这种连续的头部删除结点,在设置了哨兵位头结点的情况下,只需要检测当前头结点head中的值是否为需要删除的值,即:head->val == val。一旦满足条件,则直接改变head的位置,即head=head->next。不过需要注意,如果此时给定的链表为空,即:

此时head==nullptr,如果不检查,在检查结点的值这一条件是否满足删除所对应的条件时,由于head->val这句代码的运行顺序是先对head解引用,在访问值,此时会对nullptr解引用,造成错误。因此,应该先检测此时的head是否为nullptr

       在进了了上面的删除后,此时链表中,所有需要删除的头结点都已经被删除。但是,还需要再对链表进行一次遍历,检查链表中间结点是否存在需要删除的结点,例如:

       按照题目的要求,需要删除值为6的结点。前面说到,在测试用例中,存在空链表。因此,为了避免造成越界,设置了哨兵位头结点phead。在此处,考虑到遍历链表的过程中,需要不断访问不同的结点,因此,再设置一个专门用于遍历链表的结点cur = phead

      在进行删除时,首先检测cur->next是否为nullptr,如果为空,则说明整个链表遍历完成。如果不为空,则检测cur->next->val == val,如果条件满足,则说明cur指向的下一个结点需要删除,因此令cur->next = cur->next->next。对于此处,可能会有读者担心会不会造成越界。为了方便说明,此处给出一个临界情况:

      对于上述给出的图片,首先进行第一层检测,即:cur->next,由于此时并不为nullptr,并且cur指向的下一个结点中的值为6,因此需要对这个结点进行删除。删除时,需要获取cur->next->next,也就是值为6的结点的下一个结点。也就是nullptr,需要注意,此时并不构成越界。因为在链表的定义中,最后一个结点通常都会让其的next指向nullptr

    上面只给出了cur->next->val == val的情况,如果cur指向的下一个结点中的值不等于需要删除的值,则直接跳过该结点即可。

   在遍历结束后,需要删除掉前面人为创建的哨兵位头结点。返回head。代码如下:

class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        ListNode* phead = new ListNode(0);
        phead->next = head;
       //针对头删的情况
        while( head != nullptr && head->val == val)
        {
            phead->next = head->next;  
            head=head->next;
        }
        ListNode* cur = phead;
        while(cur->next != nullptr)
        {
            if(cur->next->val == val)
            {
                cur->next = cur->next->next;

            }
            else
            {
                cur=cur->next;
            }

        }
        delete phead;
        phead = nullptr;
        return head;

    }
};

   运行结果如下:

Leetcode.707 设计链表:

707. 设计链表 - 力扣(LeetCode)

       对于本题,依旧采用人为添加一个虚拟头结点的方式进行结点。不过需要注意,在题目中并没有给出针对链表中单个结点的结构的定义,因此,需要认为加上上述代码:

struct ListNode
    {
        ListNode(int val)
          :_val(val)
          ,_next(nullptr)
          {}
        int _val;
        ListNode* _next;

    };

在加入后,在给定的类中,将ListNode* phead,int\, \, size作为类的两个成员变量。

随后,在给定类中的构造函数,完成对于虚拟头结点的结构的初始化,即:

 MyLinkedList() {
        _size = 0;
        _phead = nullptr;
    }

其中,size表示链表中结点的数量。

对于头插函数addATHead,只需要先新建一个结点 ,为了方便表示,将这个结点命名为newnode。令newnode->next = phead->next。再phead->next = newnode。具体代码如下:

  void addAtHead(int val) {
        ListNode* newnode = new ListNode(val);
        newnode->_next = _phead->_next;
        _phead->_next = newnode;
        _size++;
    }

其效果可以用下面的图进行表示:

此处需要注意,虚拟头结点并不算一个真正的结点。在题干中说到,链表中结点的下标是从0开始的,也就是说,对于插入的第一个头结点,其下标为0

       对于返回值函数get,需要注意题中传递的参数index是返回下标为index的值。但是,由于链表中第一个结点的下标为0,也就是说,链表中的第三个结点的下标为2。但是size表示的结点的个数,因此为3。所以,在进行index合法性检测时,需要检测index>size-1index<0

      假设在链表中已有4个结点,返回结点的下标为3,即返回链表中最后一个结点。则首先定义一个指针指向第0个结点,然后向后遍历3次即可。对应代码如下:
 

int get(int index) {

        if(index > (_size-1) || index < 0)
        {
            return -1;
        }
        ListNode* cur = _phead->_next;
        while(index--)
        {
            cur = cur->_next;
        }
        return cur->_val;

    }

对于尾插函数addAtTail,原理简单,直接给出代码,不做多余解释

void addAtTail(int val) {
        ListNode* cur = _phead;
        while(cur->_next != nullptr)
        {
            cur = cur->_next;
        }

        ListNode* newnode = new ListNode(val);
        newnode->_next = cur->_next;
        cur->_next = newnode;
        _size++;

    }

       对于插入函数addAtIndex,需要注意,题目要求在index位置之前插入元素。因此,需要找到index-1这个下标所对应的元素。前面说到,由于链表中第一个结点的下标是从0开始的。所以在链表中下标为index的结点,在链表中的位置其实是index+1。例如当index==3时,对应这个下标的结点是链表中的第四个结点。对于找到这个结点,需要从第0个结点开始,向后遍历index次。此时找到的结点是链表中第index+1个结点。

     由于要求招待index位置前一个位置的结点,所以只需要改变遍历起点的位置即可,即改为哨兵位头结点。

     但是需要注意,对于index需要进行合法性检测。题目说到了,当index==size时,视作尾插,因此只需要检测index>size这一种情况即可。

     对应代码如下:
 

void addAtIndex(int index, int val) {

        if(index > _size || index < 0)
        {
            return ;
        }
        
        ListNode* cur = _phead;
       
        while(index--)
        {
            cur = cur->_next;
            
        }

        ListNode* newnode = new ListNode(val);
        newnode->_next = cur->_next;
        cur->_next = newnode;
        _size++;

    }

对于删除结点函数,需要注意,按照删除结点的逻辑,在遍历时,能遍历到的最后一个结点便是链表中倒数第二个结点。因此,如果index==size,此时会遍历到最后一个结点,在后续造成非法访问。因此,检测条件需要进行改变。对应代码如下:

 void deleteAtIndex(int index) {

        if(index >= _size || index < 0)
        {
            return ;
        }

        ListNode* cur = _phead;
        while(index--)
        {
            cur = cur->_next;
        }

        ListNode* tmp = cur->_next;
        cur->_next = cur->_next->_next;
        delete tmp;
        tmp = nullptr;
        _size--;

    }

题目整体代码如下:
 

class MyLinkedList {
public:
    
    struct ListNode
    {
        ListNode(int val)
           : _val(val)
           , _next(nullptr)
           {}

        ListNode* _next;
        int _val;
    };
    MyLinkedList() {
        
         _phead = new ListNode(0);
        _size =0;


    }
    
    int get(int index) {

        if(index > _size-1 || index < 0)
        {
            return -1;
        }
        ListNode* cur = _phead->_next;

        while(index--)
        {
            cur = cur->_next;
        }

        return cur->_val;

    }
    
    void addAtHead(int val) {

        ListNode* newnode = new ListNode(val);
        newnode->_next = _phead->_next;
        _phead->_next = newnode;
        _size++;
    }
    
    void addAtTail(int val) {

        ListNode* cur = _phead;
        while(cur->_next != nullptr)
        {
            cur = cur->_next;
        }

        ListNode* newnode = new ListNode(val);
        newnode->_next = cur->_next;
        cur->_next = newnode;
        _size++;


    }
    
    void addAtIndex(int index, int val) {

        if(index > _size || index < 0)
        {
            return ;
        }
        
        ListNode* cur = _phead;
       
        while(index--)
        {
            cur = cur->_next;
            
        }

        ListNode* newnode = new ListNode(val);
        newnode->_next = cur->_next;
        cur->_next = newnode;
        _size++;

    }
    
    void deleteAtIndex(int index) {

        if(index >= _size || index < 0)
        {
            return ;
        }

        ListNode* cur = _phead;
        while(index--)
        {
            cur = cur->_next;
        }

        ListNode* tmp = cur->_next;
        cur->_next = cur->_next->_next;
        delete tmp;
        tmp = nullptr;
        _size--;

    }

   ListNode* _phead;
   int _size;

};

运行结果如下:

Leetcode.206 反转链表:

206. 反转链表 - 力扣(LeetCode)

依旧采用人为添加哨兵位头结点的方式,原理较为简单,只给出图形表示:

先保存cur的后一个结点tmp,接着改变cur的指向,使其指向pre,对于pre可以看作哨兵位头结点,随后让pre = cur,让cur=tmp即可。一直循环该过程。

对应代码如下:
 

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        
        ListNode* cur = head;
        ListNode* pre = nullptr;
        ListNode* tmp = nullptr;
        while(cur)
        {
            tmp = cur->next;
            cur->next = pre;

            pre = cur;
            cur = tmp;
        }
        return pre;
        

    }
};

  • 22
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

起床写代码啦!

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值