

前言:前面由于忙着整理苍穹外卖,耽搁了算法的学习,进度有点慢了,下一阶段主要学习算法,再整顿一下,准备开黑马点评了,暂时先用算法过渡一下吧。
题目背景:LeetCode707
在链表类中实现这些功能:

//单链表
class MyLinkedList {
class ListNode {
int val;
ListNode next;
ListNode(int val) {
this.val=val;
}
}
//size存储链表元素的个数
private int size;
//注意这里记录的是虚拟头结点
private ListNode head;
//初始化链表
public MyLinkedList() {
this.size = 0;
this.head = new ListNode(0);
}
//获取第index个节点的数值,注意index是从0开始的,第0个节点就是虚拟头结点
public int get(int index) {
//如果index非法,返回-1
if (index < 0 || index >= size) {
return -1;
}
ListNode cur = head;
//第0个节点是虚拟头节点,所以查找第 index+1 个节点
for (int i = 0; i <= index; i++) {
cur = cur.next;
}
return cur.val;
}
public void addAtHead(int val) {
ListNode newNode = new ListNode(val);
newNode.next = head.next;
head.next = newNode;
size++;
// 在链表最前面插入一个节点,等价于在第0个元素前添加
// addAtIndex(0, val);
}
public void addAtTail(int val) {
ListNode newNode = new ListNode(val);
ListNode cur = head;
while (cur.next != null) {
cur = cur.next;
}
cur.next = newNode;
size++;
// 在链表的最后插入一个节点,等价于在(末尾+1)个元素前添加
// addAtIndex(size, val);
}
// 在第 index 个节点之前插入一个新节点,例如index为0,那么新插入的节点为链表的新头节点。
// 如果 index 等于链表的长度,则说明是新插入的节点为链表的尾结点
// 如果 index 大于链表的长度,则返回空
public void addAtIndex(int index, int val) {
if (index < 0 || index > size) {
return;
}
//找到要插入节点的前驱
ListNode pre = head;
for (int i = 0; i < index; i++) {
pre = pre.next;
}
ListNode newNode = new ListNode(val);
newNode.next = pre.next;
pre.next = newNode;
size++;
}
public void deleteAtIndex(int index) {
if (index < 0 || index >= size) {
return;
}
//因为有虚拟头节点,所以不用对index=0的情况进行特殊处理
ListNode pre = head;
for (int i = 0; i < index ; i++) {
pre = pre.next;
}
pre.next = pre.next.next;
size--;
}
}题目分析: 这是一个链表的基础操作题,类似于在数组中的增删操作,但是链表肯定是比数组要复杂一些的,但是搞懂原理实际上也没什么太大的区别。 我们在操作这些方法时,通常要设置一个虚拟头节点,不了解的我在前面详细讲解过了,主要就是为了简化操作。
由于链表没有数组的索引,获取链表值的时候我们不能直接查询,而是要从头开始遍历,直到查询到我们需要的节点。
在这里我们定义了虚拟头节点,那这样来说的话,我们是从0节点开始遍历也就是我们的虚拟头节点,但是我们需要理解的是,如果题目里要查询的index=0,这里的节点是链表的真正头节点,而到我们这里用虚拟头结点的方法的话,0节点就是虚拟头节点,因此要查询题目中给的,就是虚拟头节点之后的一个节点。
最直观的例子
head(虚拟头) → 节点10 → 节点20 → 节点30
用户认为的index: 0 1 2因此循环条件就需要index+1
虚拟头(内部0) → 真实节点A(题目0) → 真实节点B(题目1) → 真实节点C(题目2)
index = 0 → 走 1 步index = 1 → 走 2 步index = 2 → 走 3 步所以:题目要 index,我们就从虚拟头往后走 index+1 步
然后从虚拟头节点开始遍历,最后到index+1返回我们需要查询的。
void addAtHead(int val)将一个值为val的节点插入到链表中第一个元素之前。在插入完成后,新节点会成为链表的第一个节点。 题目解析: 因为我们设置了虚拟头节点,就算我们在这里要在第一个元素之前插入一个节点,也跟正常插入操作没有区别,第一个头节点的特殊性已经被我们的虚拟头节点弱化了,所有节点平级。在执行插入操作时,我们只需要把需要把虚拟头节点的next给要插入的节点的next,然后再把新插入的节点放在虚拟头节点的后面,这样就完成插入操作,这里需要注意的是顺序问题,避免覆盖操作,因为一个节点只能记录一个位置,一般是从右往左进行操作。 先连后面,再连前面 新节点先连旧头 → 虚拟头再连新节点
void addAtTail(int val) 将一个值为 val 的节点追加到链表中作为链表的最后一个元素。
题目解析: 插入到链表的最后一个元素的后面
head(虚拟头) → 节点1 → 节点2 → 节点3 → null
↑
最后一个节点:节点3next 指向 nullnull 不是节点,只是标记 “后面没了”因此我们在操作的时候,只需要找哪个节点的值等于null即可,for循环遍历,找到之后直接把cur.next给新节点,null往后移动。
ListNode cur = head;head → 20 → 10 → null
cur循环:while (cur.next != null)
void addAtIndex(int index, int val) 将一个值为 val 的节点插入到链表中下标为 index 的节点之前。如果 index 等于链表的长度,那么该节点会被追加到链表的末尾。如果 index 比长度更大,该节点将 不会插入 到链表中。
题目解析: 我们先上来校验index是否合法
之后我们就开始处理一般操作,先要找到要插入节点的前驱节点,不然插不进去,会断链。
逐行代码流程演示
我们用这个链表当例子:
plaintext
head(虚拟头) → 10 → 20 → 30 → null
size = 3
index 编号: 0 1 2现在执行:
addAtIndex(1, 99);意思:在 index=1 的位置插入 99
第 1 步:判断 index 合法
if (index < 0 || index > size) {
return;
}index=1,在 0~3 之间 ✅ 合法
第 2 步:找 前驱节点
ListNode pre = head;
for (int i = 0; i < index; i++) {
pre = pre.next;
}我们要插 index=1,所以循环 跑 1 次
流程:
✅ 前驱找到了:10
第 3 步:创建新节点
ListNode newNode = new ListNode(99);第 4 步:插入(和头插法一样)
第一步:新节点先连后面
newNode.next = pre.next;pre.next 是 20所以:
plaintext
99 → 20第二步:前驱再连新节点
pre.next = newNode;plaintext
10 → 99最终链表变成
plaintext
head → 10 → 99 → 20 → 30 → null
index: 0 1 2 3第 5 步:长度 + 1
size++;size 从 3 → 4
void deleteAtIndex(int index)如果下标有效,则删除链表中下标为index的节点。 题目解析: 因为有虚拟节点,不需要对index=0特殊处理 循环跑 index 次 → 找到要删除节点的前驱
删除 = 找前驱 → 跳过要删的节点找前驱 = 用 for 循环跑 index 次
结语:
如果对你有帮助,请点赞,关注,收藏,你的支持就是我最大的鼓励!