代码随想录 Day5 链表 | LC203 移除链表元素

本文详细介绍了链表的基础理论,包括单链表、双链表和循环链表的区别,以及链表的存储方式。重点讲解了如何在链表中删除节点,包括删除头节点和非头节点的方法,以及使用虚拟头节点统一操作的优化。最后对比了不使用虚拟头节点和使用虚拟头节点的算法效率和空间复杂度。
摘要由CSDN通过智能技术生成

一、链表基础理论

链表是一种通过指针串联在一起的线性结构,每一个节点由两部分组成,一个是数据域一个是指针域(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针的意思)。

链表的入口节点称为链表的头结点也就是head。如图所示:

链表的类型


链表分为:单链表、双链表、循环链表

单链表:

刚才说的这种链表就是单链表

双链表:

单链表中的指针域只能指向节点的下一个节点。

双链表:每一个节点有两个指针域,一个指向下一个节点,一个指向上一个节点。

双链表 既可以向前查询也可以向后查询。

循环链表:

循环链表,顾名思义,就是链表首尾相连。

循环链表可以用来解决约瑟夫环问题。

链表的存储方式


链表是通过指针域的指针链接在内存中各个节点。

所以链表中的节点在内存中不是连续分布的 ,而是散乱分布在内存中的某地址上,分配机制取决于操作系统的内存管理。

如下图:这个链表起始节点为2, 终止节点为7, 各个节点分布在内存的不同地址空间上,通过指针串联在一起

 

链表的定义


平时在刷leetcode的时候,链表的节点都默认定义好了,直接用就行了;

而在面试的时候,一旦要自己手写链表,就写的错漏百出。

// 定义一个单链表

public class ListNode {

    // 结点的值

    int val;

    // 下一个结点

    ListNode next;

    // 节点的构造函数(无参)

    public ListNode() {

    }

    // 节点的构造函数(有一个参数)

    public ListNode(int val) {

        this.val = val;

    }

    // 节点的构造函数(有两个参数)

    public ListNode(int val, ListNode next) {

        this.val = val;

        this.next = next;
    }
}

链表的操作


删除节点:

删除D节点,如图所示:

只要将C节点的next指针 指向E节点就可以了。

那么此时D节点不是依然存留在内存里么?只不过是没有在这个链表里而已。

是这样的,所以在C++里最好是再手动释放这个D节点,释放这块内存。

其他语言例如Java、Python,就有自己的内存回收机制,就不用自己手动释放了。

添加节点:

可以看出链表的增添和删除都是O(1)操作,也不会影响到其他节点。

注意,要是删除第五个节点,需要从头节点查找到第四个节点通过next指针进行删除操作,查找的时间复杂度是O(n)

性能分析


数组在定义的时候,长度就是固定的,如果想改动数组的长度,就需要重新定义一个新的数组。

链表的长度可以是不固定的,并且可以动态增删, 适合数据量不固定,频繁增删,较少查询的场景。

二、移除链表元素

题目:

力扣203:移除链表元素

给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。

题意:删除链表中等于给定值 val 的所有节点。

示例1:

输入:head = [1,2,6,3,4,5,6], val = 6

输出:[1,2,3,4,5]

示例2:

输入:head = [], val = 1

输出:[]

示例3:

输入:head = [7,7,7,7], val = 7

输出:[]

提示:

  • 列表的节点数目在范围[0,104]内
  • 1 <= Node.val <= 50
  • 0 <= val <= 50

解题思路分析


不采用虚拟头节点的方式:

要删除C节点,就让前一个节点直接指向要删除节点的下一个节点;

如果要删除头节点A,头节点没有前一个节点,需要直接把头指针指向下一个节点作为新的头节点即可;

删除元素操作:

删除头节点:

  • 首先需要判断删除的是不是头节点;头节点一定要不为空,因为接下来要取出头节点的值,如果为空的话就是在操作空指针了,编译会报错;
  • 注意,这里不能用if判断,因为if只能判断一次;如果指向的新的头节点依然是要删除的目标元素,那么还需要继续删除头节点的操作;
  • 如果头节点不为空,且他的值等于要删除的目标值,那么直接将头指针head指向下一个节点即可删除该头节点

删除非头节点:

  • 定义一个指针来找要删除的非头节点元素
  • 判断非头节点元素是否为空,避免操作空指针
  • 如果找到要删除的元素节点,就让该节点的前一个节点指向该节点的后一个节点
  • 如果没找到就往后移动指针继续找,直到指针指向空,就代表移动到了最后一个节点

代码讲解:

//删除头节点元素
//首先要判断删除的是不是头节点,同时还需要判断头结点是否为空
//注意,这里不能用if判断,因为if只能判断一次;如果指向的新的头节点依然是要删除的目标元素,那么还需要继续删除头节点的操作
 while(head != NULL && head.val == target) {
    //如果头节点不为空,且他的值等于要删除的目标值,那么直接将头指针head指向下一个节点即可删除该头节点
    head = head.next;
 }

    //删除非头节点元素(经过上面的循环,此时已经确定当前的head.val != val了)
    //定义一个指向head的指针current,因为删除元素需要让他的前一个节点指向他的后一个节点,如果current直接指向头节点后一个节点的话,单向链表就找不到他的前一个节点了;此处的逻辑是:如果current.next的值是要删除的,那么直接让current指向current.next.next即可

 ListNode cur = head;
 while(cur != NULL && cur.next != NULL) {
     if(cur.next.val == target){
        cur.next = cur.next.next;
     }else{
        cur = cur.next; //如果不相等就让cur继续往下走,判断下一个cur.next是否等于target
     }
 }

 return head; //返回操作完删除以后的链表头节点指针

由此可以看出删除头节点和删除非头节点的操作是不一样的,这样的话删除节点的方式是不统一的;

有没有可以让删除节点操作统一的方法?

有,就是 虚拟头节点 的方法

采用虚拟头节点方式:

我们可以在最前面加一个虚拟头节点,将删除头节点的操作和删除非头节点的操作统一起来;

这样我们再去删除头节点的话,实际操作和删除非头节点是一样的:

代码讲解:

//首先创建一个新节点,作为虚拟头节点
 ListNode dummy = new ListNode(-1, head);

//定义一个临时指针用来遍历链表
 ListNode cur = dummy; //这个临时指针要指向虚拟头节点,理由同样是需要找到要删除节点的前一个节点
 
 while(cur.next != NULL) {
    if(cur.next.val == target) {
        cur.next = cur.next.next;
    }else{
        cur = cur.next;
    }
}

 return dummy.next; //虚拟头节点的下一个才是新链表的头节点,不能return head,因为head指向的头节点可能已经被删掉了

题解:


不采用虚拟头节点:

  • 时间复杂度 O(n)
  • 空间复杂度 O(1)

采用虚拟头节点:

  • 时间复杂度 O(n)
  • 空间复杂度 O(1)

注意:这里不要忘记判断头指针为空的情况;(如果是空链表就直接返回head)

  • 18
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是链表元素的Python代码: ```python class ListNode: def __init__(self, val=0, next=None): self.val = val self.next = next def removeElements(head: ListNode, val: int) -> ListNode: # 处理头部节点为要删除元素的情况 while head is not None and head.val == val: head = head.next # 处理链表中间节点为要删除元素的情况 if head is not None: node = head while node.next is not None: if node.next.val == val: node.next = node.next.next else: node = node.next return head ``` 这里定义了一个ListNode类表示链表节点,其中val表示节点的值,next表示指向下一个节点的指针。removeElements函数的第一个参数head表示链表的头节点,第二个参数val表示要删除的元素值。函数的返回值是除后的链表头节点。 函数的实现分为两步: 1. 处理头部节点为要删除元素的情况,即如果头部节点的值等于要删除的元素值,则将头部节点指向下一个节点,直到头部节点的值不等于要删除的元素值。 2. 处理链表中间节点为要删除元素的情况,即从头节点开始遍历链表,如果当前节点的下一个节点的值等于要删除的元素值,则将当前节点的next指针指向下一个节点的next指针,即跳过当前节点的下一个节点;否则,将当前节点指向下一个节点。遍历完成后,返回链表的头节点。 注意,这里的实现并没有考虑链表为空的情况,需要在调用函数前进行判断。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值