深入解析:链表的核心思想
链表的核心思想
1. 数组转化为单链表
class ListNode {
int val;
ListNode next;
ListNode(int x) {val = x};
}
// 将数组转化为一个单链表
ListNode createLinkedList(int[] arr) {
if (arr == null || arr.length == 0) {
return null;
}
ListNode head = new ListNode(arr[0]);
ListNode cur = head;
for(int i = 1; i < arr.length; i++) {
cur.next = new ListNode(arr[i]);
cur = cur.next;
}
return head;
}
代码疑难点
cur.next = new ListNode(arr[i]); // 把新建的节点接到当前节点后面
cur = cur.next; // 当前指针移动到下一个节点去
2. 单链表的头插
// 创建一个单链表
ListNode head = createLinkedList(new int[]{1, 2, 3, 4, 5});
// 在单链表前面进行头插
ListNode cur = new ListNode(int[0]); // 创建一个新节点
cur.next = head;
head = cur;
代码疑难点
1 -> 2 -> 3 -> 4 -> 5
cur.next = head; // 将新节点指向了1这个节点,这个节点也就是 head
// 变成了 0 -> 1 -> 2 -> 3 -> 4 -> 5 // 此时,head 指针还停留在 1 这个节点上
head = cur; // 将 head 指针走向了 0 这个位置
3. 单链表的尾插
// 创建一条单链表
ListNode head = createLinkedList(new int[]{1, 2, 3, 4, 5});
// 在链表的尾部插入一个数字6
ListNode p = head; // 一般另起一个指针来进行遍历,不使用原来的头结点
// 首先,得将指针移动到链表尾部
while (p.next != null) {
p = p.next;
}
// 1 -> 2 -> 3 -> 4 -> 5 // 现在 p 指针指向了 5 数字
p.next = new ListNode(6);
// 这样就完成了:1 -> 2 -> 3 -> 4 -> 5 -> 6
代码疑难点
// 为什么 while(p != null || p.next != null) 不是这个?而是只需要p.next != null 即可?
// 原因就是:如果 head != null ,p 一定不会等于 null,最终会停在尾节点。
4. 在单链表中间插入新元素
// 创建一条单链表
ListNode head = createLinkedList(new int[]{1, 2, 3, 4, 5});
// 在坐标3中插入一个数字66
ListNode p = head;
for (int i = 0; i < 2; i++) {
p = p.next;
}
// 创建一个新节点
ListNode newNode = new ListNode(66);
newNode.next = p.next;
// 插入新节点
p.next = newNode;
// 最终变成了 1 -> 2 -> 3 -> 66 -> 4 -> 5
代码疑点
// 创建一个新节点
ListNode newNode = new ListNode(66);
newNode.next = p.next; // p.next 指向了4这个数字,66 -> 4
// 插入新节点
p.next = newNode; // p 的下一个节点指向了 newNode 变成了:3 -> 66
// 最终变成了 1 -> 2 -> 3 -> 66 -> 4 -> 5
5. 在单链表中删除元素
// 创建一个单链表
ListNode head = new ListNode(new int[]{1, 2, 3, 4, 5};
// 删除第 4 个节点,要操作前驱节点
ListNode p = head;
// 走到要删除节点的前驱节点,也就是3这个节点
for (int i = 0; i < 2; i++) {
p = p.next; // 往下走
}
// 删除也就是不连接 4 这个节点得了
p.next = p.next.next; // 3 的下一个下一个节点 -> 是 5
// 现在链表:1 -> 2 -> 3 -> 5
注意点:【删除】就是:将要删除的节点的前驱节点 -> 连接其后驱节点
6. 在单链表尾部删除元素
// 创建一个单链表
ListNode head = createLinkedList(new int[]{1, 2, 3, 4, 5});
// 在尾部删除元素:找到倒数第二个元素
ListNode p = head;
while(p.next.next != null) {
p = p.next; // 往下走
}
// 走到这个状态:p 指向了 4
// 不指向 5 了,以 4 为结尾好了
p.next = null;
// 现在链表变成了 1 -> 2 -> 3 -> 4
7. 在单链表头部删除元素
// 这个更简单,头节点往下走一步好了,这样就把头节点删掉了
// 创建一个单链表
ListNode head = createLinkedList(new int[]{1, 2, 3, 4, 5});
// 头节点往下走一步
head = head.next;
// 现在变成:2 -> 3 -> 4 -> 5
不过,读者会不会有一个疑问:前面的 1 应该还会指向 2 ,会不会造成内存泄漏?
答案是不会。旧的头节点指向别的节点没有关系,只要没有其他节点指向这个旧的头结点即可,这样,旧的头节点就会被垃圾回收收掉。
8. 双链表的基本操作
// 创建一条双链表
// 创建节点
class DoublyListNode {
int val;
DoublyListNode prev, next;
DoublyListNode(int x) {val = x};
}
DoublyListNode createDoublyLinkedList(int arr) {
// 判断
if (arr == null || arr.length == 0) {
return null;
}
// new 一个头节点
DoublyListNode head = new DoublyListNode(arr[0]);
DoublyListNode cur = head;
// 开始创建链表
for (int i = 1; i < arr.length; i++) {
// 双向链表,最重要的是双向
DoublyListNode newNode = new DoublyListNode(arr[i]);
cur.next = newNode; // cur -> newNode
newNode.prev = cur; // cur <- newNode
cur = cur.next; // cur 指针往下走
}
return head;
}
9. 双链表的遍历/查找/修改
// 创建一条双链表
DoublyListNode head = createDoublyLinkedList(new arr[]{1, 2, 3, 4, 5});
DoublyListNode tail = null;
// 从头节点向后遍历链表
for (DoublyListNode cur = head; cur != null; cur = cur.next) {
System.out.println(cur.val);
tail = cur; // 尾节点跟随 cur 指针的步伐
}
// 从尾节点向前遍历链表
for (DoublyListNode cur = tail; cur != null; cur = cur.prev) {
System.out.println(cur.val);
}
10. 在双链表头部插入元素
// 创建一条双链表
DoublyListNode head = createDoublyLinkedList(new arr[]{1, 2, 3, 4, 5});
// 先 new 一个节点 0
DoublyListNode newNode = new DoublyListNode(0);
newNode.next = head; // 0 -> 1
head.prev = newNode; // 0 <- 1
head = newNode; // head 往 newNode 走,因为 head 得变成头节点,原本 head 在 1 的位置,不符合规则,所以跑回 0 这个位置
// 结果变成:0 -> 1 -> 2 -> 3 -> 4 -> 5
11. 在双链表尾部插入一个新元素
// 创建一条双链表
DoublyListNode head = createDoublyLinkedList(new int[]{1, 2, 3, 4, 5});
DoublyListNode tail = head;
// 先走到链表的尾部
while (tail.next != null) {
tail = tail.next;
}
// 现在 tail 指针指向了 5 这个位置
// new 一个新的节点 6
DoublyListNode newNode = new DoublyListNode(6);
tail.next = newNode; // tail -> newNode
newNode.prev = tail; // tail <- newNode
// 现在 tail 还是指向 5
tail = newNode; // tail 顾名思义,是尾节点的意思,所以得指向 6,所以,就往 newNode 这挪,最终 tail 就指向 6
// 现在链表变成了 1 -> 2 -> 3 -> 4 -> 5 -> 6
12. 在双链表中间插入新元素
在双链表指定位置插入节点,需要调整该位置的前驱节点和后续节点的指针。
比如下面的例子,把元素 66 插入到索引 3(第 4 个节点)的位置:
// 创建一条双链表
DoublyListNode head = createDoublyLinkedList(new int[]{1, 2, 3, 4, 5});
DoublyListNode cur = head;
// 想要在索引 3(第 4 个节点)插入元素
// cur 得走到 索引 3 的前一个位置:索引 2
for (int i = 0; i < 2; i++) {
cur = cur.next;
}
// new 一个新节点 66
DoublyListNode newNode = new DoublyListNode(66);
// 得呈现一个这个状态:cur -> newNode -> cur.next
newNode.next = cur.next; // newNode -> cur.next
cur.next = newNode; // cur -> newNode
// 双链表,讲究一个双向
cur.next.prev = newNode; // newNode <- cur.next
newNode,prev = cur; // cur <- newNode
// 现在链表变成了 1 -> 2 -> 3 -> 66 -> 4 -> 5
13. 在双链表中删除一个节点
在双链表中删除元素,得调整要删除元素的前驱节点以及其后续节点的指针:
// 创建一条双链表
DoublyListNode head = createDoublyLinkedList(new int[]{1, 2, 3, 4, 5});
// 删除第 4 个节点
// 先找到第 3 个节点
DoublyListNode cur = head;
for (int i = 0; i < 2; i++) {
cur = cur.next;
}
// 现在 cur 指针指向了 3
DoublyListNode toDelete = cur.next; // toDelete 节点指向 4
// 只需要将 toDelete 的前驱节点 -> 后续节点,跳过 toDelete 就好了
cur.next = toDelete.next; // cur -> toDelete.next
toDelete.next.prev = cur; // cur <- toDelete.next
// 把 toDelete 的前后指针都去置为 null (可选,一个好习惯)
toDelete.next = null;
toDelete.prev = null;
// 现在链表变成了 1 -> 2 -> 3 -> 5
14. 在双链表头部删除元素
删除双链表需要调整头节点的指向
// 创建一条双链表
DoublyListNode head = createDoublyLinkedList(new int[]{1, 2, 3, 4, 5});
// 删除头节点,让 head 往下走一个步即可
DoublyListNode toDelete = head;
head = head.next;
// 现在 head 已经指向了 2
head.prev = null; // 将 2 的前驱节点置为空
//清除已删除节点的指针
toDelete.next = null;
// 现在链表变成了 2 -> 3 -> 4 -> 5
15. 在双链表尾部删除元素
在单链表中,由于缺乏前驱节点,所以只能遍历到尾节点的前一个节点,再进行操作,也就是:cur.next.next != null
操作它的 next 指针才能将尾节点删掉。但在双链表中,由于每个节点都存储其自身的前驱节点的指针,所以,在双链表中,可以直接操作尾节点,也就是:cur.next != null
,把自己从链表中摘掉。
// 创建一条双链表
DoublyListNode head = createDoublyLinkedList(new int[]{1, 2, 3, 4, 5});
// 删除尾节点 5
DoublyListNode cur = head;
while (cur.next != null) {
cur = cur.next;
}
// 现在 cur 走到了 5 这个位置
cur.prev.next = null; // cur.prev 指向的是 4 4 的 next 就是 5 所以把 5 给断掉了
cur.prev = null; // 把被删结点的指针都断开是个好习惯(可选)
// 现在链表变成了 1 -> 2 -> 3 -> 4
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/944647.shtml
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!相关文章
AI元人文构想:参与“自由与责任”哲学思考——岐金兰之实验
AI元人文构想:参与“自由与责任”哲学思考——岐金兰之实验
让我们以“AI元人文”构想为透镜,重新审视“自由与责任”这一古老的哲学谜题。这将不再是一场纯粹的思辨,而是一次为智能行为构建底层架构的思想实验。
基…
Java常用机制 - SPI机制详解
目录Java常用机制 - SPI机制详解简单介绍SPI工作流程SPI实现代码示例步骤 1:定义服务接口步骤 2:提供具体实现(由不同厂商提供)步骤 3:创建配置文件步骤 4:使用 ServiceLoader 发现并调用服务输出可能为:需要SP…
实用指南:用户研究:用户研究和数据分析的根本联系与区别
实用指南:用户研究:用户研究和数据分析的根本联系与区别2025-10-23 20:01
tlnshuju
阅读(0)
评论(0) 收藏
举报pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important…
2025.10.23——2绿2蓝
普及+/提高
P2914 [USACO08OCT] Power Failure G
最短路,简单预处理
P9912 [COCI 2023/2024 #2] Zatopljenje
T1,离线树状数组
提高+/省选-
P9906 [COCI 2023/2024 #1] Kocke
T2,没想到的DP
P9031 [COCI 2022/2023 …
Anaconda命令大全conda
创建虚拟环境
conda create --name myenvpython37 python=3.7使用该虚拟环境
conda activate myenvpython37退出使用虚拟环境
conda deactivate
完整教程:状态管理库 Zustand 的接入流程与注意点
完整教程:状态管理库 Zustand 的接入流程与注意点pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas"…
采用opencv来识别信用卡的号码
采用opencv来识别信用卡的号码pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco"…
塔吊施工环境与附属设施监测!思通数科 AI 卫士筑牢全场景安全防线
塔吊施工安全不仅依赖核心部件与人员操作合规,周边环境与附属设施的隐患也常成为 “安全盲区”:塔吊周边地面是否存在积水、泥泞,易引发作业人员滑倒,但人工巡检易忽视此类环境细节;有限空间入口处安全标识、警示…
精读《C++20设计模式》:重新理解设计模式系列 - 详解
精读《C++20设计模式》:重新理解设计模式系列 - 详解pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas&qu…
Kafka-保证消息消费的顺序性及高可用机制 - 教程
pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …
CSharp: Convert CSV to XLS Using Open XML SDK
using System;
using System.Data;
using System.IO;
using System.Linq;
using System.Text;
using System.Globalization;
using CsvHelper;
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging…
实用指南:PyTorch 数据处理工具箱:从数据加载到可视化的完整指南
pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …
《程序员修炼之道:从小工到专家》阅读笔记1
2025年的今天,当AI代码助手能自动生成70%基础代码时,程序员的核心竞争力究竟是什么?《程序员修炼之道》序言以"生存指南"的定位给出震撼答案:真正的高手从不依赖工具,而是凭借责任意识、持续学习与批判…
多级多卡训练模型时有些参数没有参与loss计算和梯度更新的解决办法
在运行程序的bash命令中添加 export TORCH_DISTRIBUTED_DEBUG=DETAIL ,这样就可以在log或终端打印没有参与loss计算的权重参数了。
负载均衡及三种软件负载
[!TIP]
环境用nginx反向代理文档里的三台服务器即可一、基于nginx的负载均衡
七层负载:配置nginx.conf主配置文件
vim /etc/nginx/nginx.conf
在http块内添加:
upstream userLB(随意){
#server写的ip:端口号(8080为t…
在 GEO / AIO 角度:如何优化 SEO 内容?
一、先把优化方向说清楚
现在做内容的目标不仅仅是 SEO 排第几,而是被选进 AI 答案。
AI 搜索会把页面切成可复用的小块,再按权威与相关性拼答案。传统 SEO 的可抓取、元数据、内链、外链仍是地基,但想被选中,核心…
Android Handler的runWithScissors手段
pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …