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

本文主要是介绍代码随想录算法训练营第三天| 203.移除链表元素、 707.设计链表、 206.反转链表,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

203.移除链表元素

在这里插入图片描述

题目链接: 203.移除链表元素
文档讲解:代码随想录
状态:没做出来,做题的时候定义了一个cur指针跳过了目标val遍历了一遍链表,实际上并没有删除该删的节点。

错误代码:

    public ListNode removeElements(ListNode head, int val) {ListNode sentinel = new ListNode();sentinel.next = head;ListNode cur = sentinel;while (cur.next != null) {ListNode next = cur.next;if (next.val == val) {// 这里是错误的,cur 被设置为 next.next,直接跳过了 next 节点。但这并没有实际从链表中删除 next 节点,它仍然存在于链表中。// 如果 next.next 是 null,则 cur 被设置为 null,导致后续的 cur.next 会抛出空指针异常cur = next.next;} else {cur = next;}}return sentinel.next;}

思路:定义一个ListNode类型的cur指针,遍历链表,遇到等于目标val的节点,通过修改节点的next实现删除元素。因为NodeList是引用类型,所以当cur从虚拟头节点出发时,修改cur中的引用会影响虚拟头节点后面的值,从而实现删除操作。

题解

    public ListNode removeElements(ListNode head, int val) {// 创建一个哨兵节点,简化边界情况的处理ListNode sentinel = new ListNode();sentinel.next = head;ListNode cur = sentinel;// 遍历链表while (cur.next != null) {ListNode next = cur.next;// 如果下一个节点的值等于指定值if (next.val == val) {// 应该通过修改cur.next来删除next节点cur.next = next.next;} else {// 否则,继续向后遍历cur = next;}}// 返回处理后的链表头节点return sentinel.next;}

什么时候使用虚拟头节点?
虚拟头节点(哨兵节点)主要解决了由于头节点可能为空或者需要特别处理而导致的额外操作。
如果在使用虚拟头节点后仍然从 head 节点出发,那么虚拟头节点的定义就失去了意义。

所以,当定义了虚拟头节点(哨兵节点)后,一般情况下,遍历操作中的 cur(当前节点)都从虚拟头节点出发。这是因为虚拟头节点是为了简化处理头节点的特殊情况而引入的,从虚拟头节点开始遍历,可以统一处理所有节点(包括原本的头节点),避免了额外的边界条件检查。

707.设计链表

在这里插入图片描述

题目链接:707.设计链表
文档讲解:代码随想录
状态:没做出来(使用了双向链表,没有使用虚拟头节点,结果一堆边界问题。。。)

题解

单向链表+虚拟头节点题解

public class MyLinkedList {private ListNode dummy; // 虚拟节点private int size;       // 链表的长度/** 初始化链表 */public MyLinkedList() {dummy = new ListNode(0);size = 0;}/** 获取链表中下标为 index 的节点的值,如果下标无效则返回 -1 */public int get(int index) {if (index < 0 || index >= size) {return -1;}ListNode current = dummy.next;for (int i = 0; i < index; i++) {current = current.next;}return current.val;}/** 在链表头部插入值为 val 的节点 */public void addAtHead(int val) {ListNode newHead = new ListNode(val);newHead.next = dummy.next;dummy.next = newHead;size++;}/** 在链表尾部追加值为 val 的节点 */public void addAtTail(int val) {ListNode newTail = new ListNode(val);ListNode current = dummy;while (current.next != null) {current = current.next;}current.next = newTail;size++;}/** 在链表中下标为 index 的节点之前插入值为 val 的节点 */public void addAtIndex(int index, int val) {if (index < 0 || index > size) {return;}
// 注意:不是ListNode current = dummy.next;
// 简而言之,ListNode cur = dummy; 确保了在插入节点时,从链表的头节点开始遍历,而不是跳过头节点。
// 例如index=0,则是dummy.next = newNodeListNode current = dummy;for (int i = 0; i < index; i++) {current = current.next;}ListNode newNode = new ListNode(val);newNode.next = current.next;current.next = newNode;size++;}/** 删除链表中下标为 index 的节点 */public void deleteAtIndex(int index) {if (index < 0 || index >= size) {return;}ListNode current = dummy;for (int i = 0; i < index; i++) {current = current.next;}current.next = current.next.next;size--;}static class ListNode {int val;ListNode next;// 节点构造函数public ListNode(int val) {this.val = val;}}
}

双向链表无虚拟头节点题解(不推荐)

class MyLinkedList {ListNode head;static class ListNode {int val;ListNode pre;ListNode next;public ListNode() {}public ListNode(int val, ListNode pre, ListNode next) {this.val = val;this.pre = pre;this.next = next;}@Overridepublic String toString() {return "ListNode{" +"val=" + val +", pre=" + pre +", next=" + next +'}';}}public MyLinkedList() {head = null;}public int get(int index) {if (index < 0 || head == null) {return -1;}ListNode cur = head;while (index > 0 && cur != null) {cur = cur.next;index--;}return (cur == null) ? -1 : cur.val;}public void addAtHead(int val) {ListNode node = new ListNode(val, null, head);if (head != null) {head.pre = node;}head = node;}public void addAtTail(int val) {ListNode node = new ListNode(val, null, null);ListNode cur = head;while (cur.next != null) {cur = cur.next;}cur.next = node;node.pre = cur;}public void addAtIndex(int index, int val) {if (index < 0) {return;}if (index == 0) {addAtHead(val);return;}ListNode cur = head;while (index > 1 && cur != null) {cur = cur.next;index--;}if (cur == null) {return;}ListNode node = new ListNode(val, cur, cur.next);if (cur.next != null) {cur.next.pre = node;}cur.next = node;}public void deleteAtIndex(int index) {if (index < 0 || head == null) {return;}if (index == 0) {head = head.next;if (head != null) {head.pre = null;}return;}ListNode cur = head;while (index > 0 && cur != null) {cur = cur.next;index--;}if (cur == null) {return;}if (cur.pre != null) {cur.pre.next = cur.next;}if (cur.next != null) {cur.next.pre = cur.pre;}}
}

206.反转链表

在这里插入图片描述

题目链接: 206.反转链表
文档讲解:代码随想录
状态:没做出来,明明很简单的题,为啥没做出来呢。。。。。想着用dummy节点,dummy->1->2->3… dummy->2->1->3… dummy->3->2->1->…,这个dummy的next一直在变。。。

思路:

在这里插入图片描述

题解

    public ListNode reverseList(ListNode head) {// 初始化前一个节点为nullListNode prev = null;// 当前节点从head开始ListNode curr = head;while (curr != null) {// 暂时保存下一个节点ListNode next = curr.next;// 当前节点的next指向前一个节点curr.next = prev;// 移动前一个节点到当前节点prev = curr;// 移动当前节点到下一个节点curr = next;}// 最后prev将指向新的头节点return prev;}

总结反思

为啥做题的时候总是有很多乱七八糟的思路,自己找的还总是最差的那种呢。。。。

链表题解的两个技巧
遇到链表相关的题,无论问题是什么,先要想想是不是可以用上以下的两个技巧。

  1. 哨兵节点:哨兵节点是一个非常常用的链表技巧,在处理链表边界问题的场景下,可以减少我们代码的复杂度。
    主要解决的问题如下:
    • 处理完一条链表后,需要返回这个链表的头结点。我们在一开始的时候使用哨兵节点(dummy),让它的 next 节点指向 head 节点。最后 return 时直接返回 dummy.next 即可。
    • 在对链表进行插入或删除节点时,使用哨兵节点可以简化删除 head 节点或者向 head 前插入节点时的处理逻辑。因为头节点没有前驱节点,这就导致对其的增删需要额外操作。
    • 在某些遍历链表的时候,可能会需要同时记录 pre 节点。当你从 head 节点开始遍历时,head 是没有 pre 节点的(为null)。而此时引用哨兵节点,相当于帮助 head 节点初始化了一个 pre 节点,可以方便的解决 pre 节点为空的问题

为啥反转链表这道题不用虚拟头结点呢?

对于反转链表的操作,无论是递归还是迭代方法,都集中在反转节点的指针上,而不涉及节点的插入或删除操作。因此,反转链表的核心在于调整指针的方向,而不是处理节点的前驱节点或边界条件。这也是为什么在反转链表时,虚拟头节点并不能带来实际的简化。

  1. 双指针:其实不止是链表,对于数组题来说,也是非常好用的技巧。
    双指针的主要用法有以下几种:
    • 两个指针在两条链表上行走:这种方法通常用于合并两个有序链表、找到两个链表的交点等问题。
    • 快慢指针,同时前进:快慢指针通常用于检测链表中是否存在环,或者找到链表的中间节点等情况。
    • 前后指针,前指针先走 n 步,之后两个指针同时前进:这种方法常用于找到链表的倒数第 n 个节点,或者解决某些数组问题。
    • 一个指针用来迭代遍历链表,另一个指针用来记录:在链表操作中,常常需要用一个指针来迭代遍历链表,另一个指针用来记录节点,例如 pre 和 cur;cur 和 next;pre、cur 和 next。

这篇关于代码随想录算法训练营第三天| 203.移除链表元素、 707.设计链表、 206.反转链表的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/999592

相关文章

Python实例题之pygame开发打飞机游戏实例代码

《Python实例题之pygame开发打飞机游戏实例代码》对于python的学习者,能够写出一个飞机大战的程序代码,是不是感觉到非常的开心,:本文主要介绍Python实例题之pygame开发打飞机... 目录题目pygame-aircraft-game使用 Pygame 开发的打飞机游戏脚本代码解释初始化部

Java中Map.Entry()含义及方法使用代码

《Java中Map.Entry()含义及方法使用代码》:本文主要介绍Java中Map.Entry()含义及方法使用的相关资料,Map.Entry是Java中Map的静态内部接口,用于表示键值对,其... 目录前言 Map.Entry作用核心方法常见使用场景1. 遍历 Map 的所有键值对2. 直接修改 Ma

Linux链表操作方式

《Linux链表操作方式》:本文主要介绍Linux链表操作方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、链表基础概念与内核链表优势二、内核链表结构与宏解析三、内核链表的优点四、用户态链表示例五、双向循环链表在内核中的实现优势六、典型应用场景七、调试技巧与

MyBatis设计SQL返回布尔值(Boolean)的常见方法

《MyBatis设计SQL返回布尔值(Boolean)的常见方法》这篇文章主要为大家详细介绍了MyBatis设计SQL返回布尔值(Boolean)的几种常见方法,文中的示例代码讲解详细,感兴趣的小伙伴... 目录方案一:使用COUNT查询存在性(推荐)方案二:条件表达式直接返回布尔方案三:存在性检查(EXI

深入解析 Java Future 类及代码示例

《深入解析JavaFuture类及代码示例》JavaFuture是java.util.concurrent包中用于表示异步计算结果的核心接口,下面给大家介绍JavaFuture类及实例代码,感兴... 目录一、Future 类概述二、核心工作机制代码示例执行流程2. 状态机模型3. 核心方法解析行为总结:三

python获取cmd环境变量值的实现代码

《python获取cmd环境变量值的实现代码》:本文主要介绍在Python中获取命令行(cmd)环境变量的值,可以使用标准库中的os模块,需要的朋友可以参考下... 前言全局说明在执行py过程中,总要使用到系统环境变量一、说明1.1 环境:Windows 11 家庭版 24H2 26100.4061

pandas实现数据concat拼接的示例代码

《pandas实现数据concat拼接的示例代码》pandas.concat用于合并DataFrame或Series,本文主要介绍了pandas实现数据concat拼接的示例代码,具有一定的参考价值,... 目录语法示例:使用pandas.concat合并数据默认的concat:参数axis=0,join=

C#代码实现解析WTGPS和BD数据

《C#代码实现解析WTGPS和BD数据》在现代的导航与定位应用中,准确解析GPS和北斗(BD)等卫星定位数据至关重要,本文将使用C#语言实现解析WTGPS和BD数据,需要的可以了解下... 目录一、代码结构概览1. 核心解析方法2. 位置信息解析3. 经纬度转换方法4. 日期和时间戳解析5. 辅助方法二、L

Python使用Code2flow将代码转化为流程图的操作教程

《Python使用Code2flow将代码转化为流程图的操作教程》Code2flow是一款开源工具,能够将代码自动转换为流程图,该工具对于代码审查、调试和理解大型代码库非常有用,在这篇博客中,我们将深... 目录引言1nVflRA、为什么选择 Code2flow?2、安装 Code2flow3、基本功能演示

IIS 7.0 及更高版本中的 FTP 状态代码

《IIS7.0及更高版本中的FTP状态代码》本文介绍IIS7.0中的FTP状态代码,方便大家在使用iis中发现ftp的问题... 简介尝试使用 FTP 访问运行 Internet Information Services (IIS) 7.0 或更高版本的服务器上的内容时,IIS 将返回指示响应状态的数字代