链表的删除—做题总结 203. 移除链表元素

一、最初的思路

设置了慢节点p1和快节点p2,遍历链表时p1紧跟p2,如果p2->val = val,那么通过 p1->next = p2->next; 把p2节点从节点中删除。

1、链表为空

直接返回head

 if(head == nullptr) return head;

2、删除的节点为头节点

头节点需要指向头结点的next
head = head->next;

注:这里可能会考虑到单节点的情况,[head]->null,如果head->val=val,那就要删除head,剩下的是null,head = head->next;是可以包括这种情况的

3、删除的节点不是头节点

class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        //1、删除节点为空
        if(head == nullptr) return head;
        ListNode* p1 = nullptr;
        ListNode* p2 = head;
        ListNode* temp = nullptr;
        while(p2 != nullptr) {
            if(p2->val == val) {
                //2、删除的是头节点
                if(p1 == nullptr) {
                    head = head->next;
                    p2 = head;
                    /*因为要删除链表中《所有》值为val的节点,
                    所以要遍历完整个链表
                    这里的p2=head保证了后续链表的遍历*/
                }
                else { //3、删除的不是头节点
                    p1->next = p2->next;//把p2节点从链表删除
                    p2 = p1->next;
                    /*p2 = p1->next;保证遍历完整个链表*/
                }
            }
            else {
                temp = p2;
                p2 = p2->next;
                p1 = temp;
            }
        }
        return head;
    }
};

运行成功。
在这里插入图片描述

二、看了题解后的优化

看了题解发现可以简化写法。核心思想是增加一个哑节点。这样情况1(链表为空)情况2(删除的节点为头节点)都可以合并进 情况3中(删除的节点不是头节点)。

需要注意的点

(1)链表中节点的构造
力扣官方对单链表的定义:

  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) {}
  };

由此,创造一个节点可以写成:

struct ListNode* dummyHead = new ListNode(0, head);

(2)上文中的情况三“删除的节点不是头节点”,通过快慢指针删除对应的节点,可以通过一个指针temp完成

   struct ListNode* temp;
 temp->next = temp->next->next;

代码如下:

class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        struct ListNode* fhead = new ListNode(0,head);//1
        struct ListNode* temp = fhead;
        //2思考这里为什么要用temp->next 而不是temp
        //尾节点 头节点
        while(temp->next != NULL) {
            if(temp->next->val == val) {
                temp->next = temp->next->next;
            }
            else {//3
                temp = temp->next;
            }
        }
        return fhead->next;//4
    }
};

犯了很多错误:

(1)哑节点的构造

题目的构造体没有用 typedef,不可以省略关键字 struct

struct ListNode* fhead = new ListNode(0,head);

//这样是错的,因为构造体并没有用typedef
ListNode* fhead = new ListNode(0,head);

(2)构造了哑节点 fhead 后的返回值

构造了哑节点 fhead 之后,注意返回值是 fhead->next,fhead 本身是不属于单链表的。

return fhead->next;

(3)temp->next 和 temp

为什么要比较 temp->next 节点不能用 temp 节点呢?
删除节点的关键步骤为 temp->next = temp->next->next; (判断的是 temp->next 这个节点)。本质上其实是用temp代替p1,temp->next代替 p2。

但如果判断变成temp这个节点(也就是假如判断的条件是if(temp->val = val;) ),要删除temp节点的话,是需要temp的上一个节点的,这里只用了temp遍历,是找不到上一个节点的。

为什么 while 语句中的判断条件是temp->next = NULL
遍历结束的条件是,temp->next = NULL;

(4)nullptr 和 NULL的区别。

接上一条,while(temp->next != NULL) 和 while(temp->next != nullptr) 的区别是什么?

NULL属于C 语言中的宏,后来 C++11 引入了 nullptr 关键字,都用来表示空指针。
在 C++ 中表示指针的地方,使用 nullptr 表示空指针。

https://blog.csdn.net/jiey0407/article/details/125383209?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522169960180916800182157513%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=169960180916800182157513&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allsobaiduend~default-1-125383209-null-null.142v96pc_search_result_base6&utm_term=nullptr%20%E5%92%8C%20NULL&spm=1018.2226.3001.4187

(5)while循环里面,如果写成如下形式 是错的:

         while(temp->next != NULL) {
            if(temp->next->val == val) {
                temp->next = temp->next->next;
            }
            temp = temp->next;
         }

会报错:
Line 19: Char 21: runtime error: member access within null pointer of type ‘struct ListNode’ (solution.cpp)
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior prog_joined.cpp:28:21

在这里插入图片描述
为什么一定需要那个else?
代码是通过判断 temp->next 这个节点的值是否为 val 进行删除操作的。

如图:假设删除了temp->next(a2),之后,a3会变成新的temp->next节点,所以要对a3进行判断。
在这里插入图片描述
因此在进行了删除操作 temp->next = temp->next->next; 之后,temp的位置是不能变化的

如果不进行删除操作,代表此时的 temp->next->val != val ,就需要把 temp往后移 temp = temp->next;

因此 else是必须要的

三、释放内存

C++编写的代码都需要释放内存,也就是上述所有代码其实都不完全正确,但是在力扣中是可以通过的。一定要注意释放内存。
delete temp;

四、Java实现

改于2023.12.11,之前是用C++写的,最近在学Java,用Java重写了一次。

class Solution {
    public ListNode removeElements(ListNode head, int val) {
        //链表删除
        //空链表
        //非空链表 添加头节点
        if(head == null) return null;

        ListNode myheah = new ListNode(-1,head);
        //这里需不需要一个临时变量呢?
        ListNode myh = myheah;
        while(myh.next != null) {//
            if(myh.next.val == val){
                myh.next = myh.next.next;
            } else {
                myh = myh.next;
            }
        }

        return myheah.next;
    }
}

分析:
注意这里是需要临时变量的。
new ListNode(-1,head) new在堆中开辟了一个空间,开辟空间的同时分配了一个地址,存在 myheah 引用中。如果遍历的时候用 myheah 这个变量,那while循环之后,链表的头节点地址是不清楚的。

四、递归解法

(还未完成)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值