1.力扣题目链接
题意:
在链表类中实现这些功能:
- get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1。
- addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。
- addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。
- addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val 的节点。如果 index 等于链表的长度,则该节点将附加到链表的末尾。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。
- deleteAtIndex(index):如果索引 index 有效,则删除链表中的第 index 个节点。
2.单链表
1)
class ListNode {
int val; // 定义一个整数型的成员变量 val,用来存储节点的值
ListNode next;// 定义一个 ListNode 类型的成员变量 next,用来存储指向下一个节点的引用
ListNode() {
}// 无参数的构造函数,初始化一个新的节点,val 默认为 0,next 默认为 null
ListNode(int val) {
this.val = val;
// 带有整数参数的构造函数,初始化一个新的节点,val 被设置为传入的参数,next 默认为 null
}
}
这段代码定义了一个名为 ListNode 的类,它代表了单链表中的节点。每个节点都有两个成员变量,val 用于存储节点的值,next 是一个引用,用于指向下一个节点。这个类提供了两个构造函数,一个是无参数的构造函数,用于创建一个值为 0 且没有下一个节点的节点,另一个是带有整数参数的构造函数,用于创建一个指定值的节点,但没有下一个节点。
2)
class MyLinkedList {
int size; // 存储链表元素的个数
ListNode head; // 虚拟头结点
// 初始化链表
public MyLinkedList() {
size = 0; // 初始时链表元素个数为0
head = new ListNode(0); // 创建一个虚拟头结点,值为0,但不包含实际数据
}
// 获取第index个节点的数值,注意index是从0开始的,第0个节点就是头结点
public int get(int index) {
// 如果index非法,返回-1
if (index < 0 || index >= size) {
return -1;
}
ListNode currentNode = head; // 从虚拟头结点开始
// 包含一个虚拟头节点,所以查找第 index+1 个节点
for (int i = 0; i <= index; i++) {
currentNode = currentNode.next; // 通过.next属性移动到下一个节点
}
return currentNode.val; // 返回第index个节点的值
}
}
这段代码定义了一个链表,其中包括初始化链表和获取链表中指定位置节点值的功能。
-
构造函数
MyLinkedList()初始化一个空链表,其中size初始化为0,而head是一个虚拟头结点,值为0,但它并不包含实际的数据,只是用来占据头结点的位置。 -
get(int index)方法用于获取链表中指定位置index的节点值。它首先检查index是否合法(即在 0 到size-1的范围内),如果不合法,则返回 -1。然后,它通过循环遍历链表,从虚拟头结点开始,找到第index个节点,并返回该节点的值。
这是一个基本的链表实现,用于演示链表的基本操作,例如获取节点值。在实际应用中,可以根据需要扩展该链表,添加其他操作如插入、删除等。
index < 0 || index >= size
在这段代码中,index 表示要获取的节点在链表中的位置,而 size 表示链表当前的元素个数。因此,当 index 大于或等于 size 时,表示要获取的节点位置超出了链表的范围,这是一个非法操作。
链表的位置是从 0 开始计数的,所以有效的位置范围是从 0 到 size-1。如果 index 小于 0 或大于等于 size,则意味着要么获取一个负数位置的节点,要么获取一个超出链表长度的位置的节点,这都是不合法的操作,因此在这种情况下,代码返回 -1,表示获取失败。
这种处理方式是为了确保在获取链表中的节点时,不会发生越界访问的情况,从而保证了代码的稳定性和健壮性。如果在实际应用中需要执行其他操作(例如插入、删除等),也应该首先检查 index 是否合法,以避免出现类似的问题。
3)
//在链表最前面插入一个节点,等价于在第0个元素前添加
public void addAtHead(int val) {
addAtIndex(0, val);
}
//在链表的最后插入一个节点,等价于在(末尾+1)个元素前添加
public void addAtTail(int val) {
addAtIndex(size, val);
}
// 在第 index 个节点之前插入一个新节点,例如index为0,那么新插入的节点为链表的新头节点。
// 如果 index 等于链表的长度,则说明是新插入的节点为链表的尾结点
// 如果 index 大于链表的长度,则返回空
public void addAtIndex(int index, int val) {
if (index > size) {
return;
}
if (index < 0) {
index = 0;
}
size++;
//找到要插入节点的前驱
ListNode pred = head;
for(int i = 0; i < index; i++){
pred = pred.next;
}
ListNode toAdd = new ListNode(val);
toAdd.next = pred.next;
pred.next = toAdd;
}
-
addAtHead(int val)方法用于在链表最前面插入一个节点,也就是在第 0 个位置之前插入节点。它等价于调用addAtIndex(0, val)方法。 -
addAtTail(int val)方法用于在链表的最后插入一个节点,也就是在链表的末尾之后插入节点。它等价于调用addAtIndex(size, val)方法,其中size表示链表的当前长度,所以新节点会添加到末尾的位置。 -
addAtIndex(int index, int val)方法用于在指定位置index之前插入一个新节点,并将值设置为val。该方法首先检查index是否合法,如果index大于链表的长度size,则不执行任何操作,直接返回。如果index小于 0,则将其设为 0,以确保在链表头部插入。然后,它逐步找到要插入节点位置的前驱节点pred,然后创建一个新的节点toAdd,将新节点的next指向pred.next,然后将pred.next指向新节点toAdd,从而将新节点插入到链表中。
这些方法允许您以不同的方式向链表中插入节点,包括在头部、尾部和指定位置插入。这些操作是链表常见的基本操作之一,用于构建和修改链表。
size++;
//找到要插入节点的前驱
ListNode pred = head;
for(int i = 0; i < index; i++){
pred = pred.next;
}
ListNode toAdd = new ListNode(val);
toAdd.next = pred.next;
pred.next = toAdd;
这段代码的主要作用是在指定位置 index 之前插入一个新的节点,并将其值设置为 val。下面是这段代码的逐行解释:
-
size++;:这一行代码增加了链表的size变量的值,表示链表中元素的个数增加了一个。这是因为在插入新节点之后,链表的长度会增加。 -
ListNode pred = head;:这一行代码创建了一个名为pred的节点对象,并将其初始化为链表的虚拟头结点head。pred用于存储要插入节点的前驱节点。 -
for(int i = 0; i < index; i++) { pred = pred.next; }:这是一个循环,它从虚拟头结点开始,通过pred依次遍历链表,直到找到要插入节点位置的前一个节点。循环中的i从 0 开始,每次迭代都将pred移动到下一个节点,直到i的值等于index,此时pred就指向了要插入节点的前驱节点。 -
ListNode toAdd = new ListNode(val);:这一行代码创建了一个新的节点toAdd,并将其值初始化为传入的参数val。 -
toAdd.next = pred.next;:这一行代码将新节点toAdd的next指向pred的next,也就是指向了要插入节点位置的后继节点。 -
pred.next = toAdd;:最后,这一行代码将pred的next指向新节点toAdd,完成了插入操作。这时,新节点toAdd被插入到pred和pred.next之间,链表的结构发生了变化,新节点成为了链表中的一个元素。
通过这些步骤,新节点 toAdd 成功地插入到指定位置 index 之前,完成了链表插入操作。
初始链表: 1 -> 3 -> 5
现在,我们要在索引 1 处(从 0 开始计数)插入一个新节点,值为 2。
-
size++;:原链表的size值为 3,表示链表中有 3 个元素。执行这行代码后,size增加 1,变为 4。 -
ListNode pred = head;:创建一个名为pred的节点,并将其初始化为链表的虚拟头结点head。此时pred指向虚拟头结点。 -
for(int i = 0; i < index; i++) { pred = pred.next; }:这是一个循环,从虚拟头结点开始,依次移动pred指向链表中的节点,直到i的值等于index,也就是1。在这个过程中,pred将指向索引1之前的节点,也就是节点1。 -
ListNode toAdd = new ListNode(val);:创建一个新的节点toAdd,其值初始化为2。 -
toAdd.next = pred.next;:将新节点toAdd的next指向pred的next。在我们的例子中,pred的next是节点3。 -
pred.next = toAdd;:将pred的next指向新节点toAdd。这一步相当于将新节点toAdd插入到pred和pred.next之间,即在索引1处插入节点2。
最终,链表变为:
新链表: 1 -> 2 -> 3 -> 5
通过这段代码,我们成功地在索引 1 处插入了一个值为 2 的新节点,同时更新了链表的 size。这展示了如何使用这段代码在指定位置插入新节点。
4)
//删除第index个节点
public void deleteAtIndex(int index) {
if (index < 0 || index >= size) {
return;
}
size--;
if (index == 0) {
head = head.next;
return;
}
ListNode pred = head;
for (int i = 0; i < index; i++) {
pred = pred.next;
}
pred.next = pred.next.next;
}
}
这段代码是用于在链表中删除指定位置 index 处的节点的操作。以下是这段代码的逐行解释:
-
if (index < 0 || index >= size):这一行代码首先检查index是否合法,即是否在链表范围内。如果index小于 0 或大于等于链表的长度size,则表示要删除的节点位置不合法,直接返回,不执行任何操作。 -
size--;:如果要删除的节点位置合法,首先将链表的size减1,表示链表中的元素数量减少了一个。 -
if (index == 0):这一行代码检查是否要删除的节点位置是链表的头节点,即index是否为 0。如果是头节点,就执行下面的操作:head = head.next;:将链表的头结点head更新为头结点的下一个节点head.next,从而跳过了原头节点,实现了删除操作。然后直接返回,操作完成。
-
如果
index不是头节点,那么程序会执行以下操作:-
ListNode pred = head;:创建一个名为pred的节点,并将其初始化为链表的虚拟头结点head。这个节点pred用来找到要删除节点的前驱节点。 -
for (int i = 0; i < index; i++) { pred = pred.next; }:这是一个循环,从虚拟头结点开始,依次移动pred指向链表中的节点,直到i的值等于index。在这个过程中,pred将指向要删除节点位置的前一个节点。 -
pred.next = pred.next.next;:最后,将前驱节点pred的next指向要删除节点的下一个节点pred.next.next,从而跳过了要删除的节点,完成删除操作。
-
通过这段代码,您可以在链表中删除指定位置 index 处的节点,如果 index 合法且不是头节点,它将执行相应的删除操作。这是链表中的基本删除操作之一。
初始链表: 1 -> 2 -> 3 -> 4 -> 5
现在,我们要删除链表中的一些节点。
-
删除索引为
2的节点(值为3):-
if (index < 0 || index >= size)检查index是否合法,2是合法的。 -
size--;减小链表的size,变为4。 -
因为
index不是头节点,所以程序进入下面的操作:-
ListNode pred = head;创建pred并初始化为虚拟头结点head。 -
for (int i = 0; i < index; i++) { pred = pred.next; }在循环中,pred移动到索引为2的前驱节点pred,即节点2。 -
pred.next = pred.next.next;将前驱节点pred的next指向节点3的下一个节点,即节点4,从而删除了节点3。
-
最终链表变为:1 -> 2 -> 4 -> 5
-
-
删除索引为
0的节点(值为1):-
if (index < 0 || index >= size)检查index是否合法,0是合法的。 -
size--;减小链表的size,变为3。 -
因为
index是头节点,所以程序进入下面的操作:head = head.next;直接将虚拟头结点head更新为下一个节点,即节点2,从而删除了节点1。
最终链表变为:2 -> 4 -> 5
-
-
删除索引为
3的节点(值为5):-
if (index < 0 || index >= size)检查index是否合法,3不合法,因为链表的长度只有3。 -
由于
index不合法,不执行任何操作。
-
最终,通过这段代码,我们可以删除链表中指定位置的节点,确保操作合法并正确更新链表。
3.双链表
1)
//双链表
class ListNode {
int val;
ListNode next, prev;
ListNode() {
}
ListNode(int val) {
this.val = val;
}
}
这段代码定义了一个名为 ListNode 的类,用于表示双向链表(Doubly Linked List)中的节点。双向链表是一种链表数据结构,每个节点不仅包含一个指向下一个节点的引用(next),还包含一个指向前一个节点的引用(prev),这使得在双向链表中可以双向遍历链表,而不仅仅是单向遍历。
让我逐行解释这段代码:
-
int val;:这是一个整数类型的成员变量,用于存储节点的值。 -
ListNode next, prev;:这两个成员变量分别是指向下一个节点和前一个节点的引用。next表示指向链表中的下一个节点,而prev表示指向链表中的前一个节点。 -
ListNode() { }:这是一个无参数的构造函数,用于创建一个新的节点对象。在这个构造函数中,没有初始化节点的值和引用,默认情况下会被初始化为默认值(对于整数是0,对于引用类型是null)。 -
ListNode(int val) { this.val = val; }:这是一个带有整数参数的构造函数,用于创建一个新的节点对象,并初始化节点的值为传入的整数值。这个构造函数可以用来创建节点并设置初始值。
总之,这段代码定义了一个用于双向链表的节点类 ListNode,它包含了节点的值以及两个引用,允许在链表中双向遍历节点。这种双向链表在某些情况下对于特定的操作非常有用,例如在删除节点时可以更容易地更新前一个节点的 next 引用。
2)
class MyLinkedList {
//记录链表中元素的数量
int size;
//记录链表的虚拟头结点和尾结点
ListNode head, tail;
public MyLinkedList() {
//初始化操作
this size = 0;
this head = new ListNode(0);
this tail = new ListNode(0);
//这一步非常关键,否则在加入头结点的操作中会出现null.next的错误!!!
head.next = tail;
tail.prev = head;
}
public int get(int index) {
//判断index是否有效
if (index < 0 || index >= size) {
return -1;
}
ListNode cur = this.head;
//判断是哪一边遍历时间更短
if (index >= size / 2) {
//tail开始
cur = tail;
for (int i = 0; i < size - index; i++) {
cur = cur.prev;
}
} else {
for (int i = 0; i <= index; i++) {
cur = cur.next;
}
}
return cur.val;
}
-
记录链表中元素的数量
size。 -
记录链表的虚拟头结点
head和虚拟尾结点tail。这两个虚拟节点用于简化链表操作,并且它们不存储实际的数据,只是用来占位。 -
初始化链表的构造函数
MyLinkedList()。
让我逐行解释这段代码的具体含义:
-
int size;:这是一个整数变量,用来记录链表中元素的数量。 -
ListNode head, tail;:这是两个ListNode类型的变量,分别表示链表的虚拟头结点和虚拟尾结点。这两个节点并不存储实际数据,只是用来帮助管理链表的结构。 -
构造函数
MyLinkedList():size初始化为 0,表示链表中还没有元素。- 创建了一个虚拟头结点
head和一个虚拟尾结点tail,它们的值都初始化为 0。 head.next = tail;和tail.prev = head;这两行代码非常重要,它们将虚拟头结点head的next指向虚拟尾结点tail,同时将虚拟尾结点tail的prev指向虚拟头结点head,这样就形成了一个初始状态下只有虚拟头尾结点的链表,确保链表的基本结构正确。
-
public int get(int index)方法:- 首先,它判断输入的
index是否有效,即是否在链表的合法范围内。 - 然后,它根据
index的位置决定从哪一端(头结点还是尾结点)开始遍历链表。这是一个优化措施,如果index靠近链表的中间位置,从尾结点开始遍历会更快。 - 最后,它使用循环遍历链表,找到指定位置
index处的节点,并返回该节点的值。
- 首先,它判断输入的
总之,这段代码定义了一个包含虚拟头尾结点的链表,通过这种方式,可以在链表的开头和结尾执行添加和删除操作,同时提供了一个 get 方法用于获取指定位置的节点值。这种设计可以提高链表的操作效率。
public int get(int index) {
//判断index是否有效
if (index < 0 || index >= size) {
return -1;
}
ListNode cur = this.head;
//判断是哪一边遍历时间更短
if (index >= size / 2) {
//tail开始
cur = tail;
for (int i = 0; i < size - index; i++) {
cur = cur.prev;
}
} else {
for (int i = 0; i <= index; i++) {
cur = cur.next;
}
}
return cur.val;
}
这段代码是 MyLinkedList 类中的 get(int index) 方法,用于获取链表中指定位置 index 处的节点值。让我逐行解释这段代码的功能:
-
if (index < 0 || index >= size):这一行代码首先检查传入的index是否有效,即是否在链表的合法范围内。如果index小于 0 或大于等于链表的长度size,则表示要获取的位置不合法,返回 -1 表示获取失败。 -
ListNode cur = this.head;:创建一个名为cur的节点引用,并将其初始化为链表的虚拟头结点head。这是开始遍历链表的起始点。 -
if (index >= size / 2):这一行代码判断从哪一端开始遍历链表会更快。它通过比较index和size的一半来决定。如果index大于等于size的一半,就从链表的尾结点tail开始往前遍历,因为距离尾部更近。这是一种优化,可以减少遍历的次数,提高效率。 -
cur = tail;:如果从尾部开始遍历,则将cur更新为链表的虚拟尾结点tail,准备从尾部向前遍历。 -
for (int i = 0; i < size - index; i++) { cur = cur.prev; }:这是一个循环,用于从cur开始向前遍历链表。循环的次数等于size - index,这是因为要走到指定位置index,需要走size - index步,每一步通过cur.prev向前移动。 -
else分支:如果从头部开始遍历,程序会进入这个分支。 -
for (int i = 0; i <= index; i++) { cur = cur.next; }:这是一个循环,用于从cur开始向后遍历链表。循环的次数等于index,每一步通过cur.next向后移动。 -
return cur.val;:最后,返回cur节点的值,即指定位置index处的节点值。
这段代码实现了根据指定位置 index 获取链表中节点值的功能,根据 index 的值,它可以选择从头部或尾部开始遍历链表,以提高效率。如果 index 无效,则返回 -1 表示获取失败。
初始链表: 1 -> 2 -> 3 -> 4 -> 5
现在,让我们使用 get 方法来获取不同位置的节点值。
-
获取索引为
2的节点值:javaCopy codeint value = myLinkedList.get(2);- 首先,检查
index是否有效,2是有效的。 - 根据优化,从尾部开始遍历链表,所以
cur被更新为虚拟尾结点tail。 - 然后,进入循环,循环次数为
size - index,即5 - 2 = 3次。 - 在每次循环中,
cur向前移动到前一个节点,最终指向索引为2的节点,其值为3。 - 返回
cur.val,即节点值3。
- 首先,检查
-
获取索引为
0的节点值:javaCopy codeint value = myLinkedList.get(0);- 首先,检查
index是否有效,0是有效的。 - 根据优化,从头部开始遍历链表,所以
cur保持为虚拟头结点head。 - 进入循环,循环次数为
index,即0次。 - 直接返回
cur.val,即节点值1。
- 首先,检查
-
获取索引为
4的节点值:javaCopy codeint value = myLinkedList.get(4);- 首先,检查
index是否有效,4是有效的。 - 根据优化,从尾部开始遍历链表,所以
cur被更新为虚拟尾结点tail。 - 进入循环,循环次数为
size - index,即5 - 4 = 1次。 - 在每次循环中,
cur向前移动到前一个节点,最终指向索引为4的节点,其值为5。 - 返回
cur.val,即节点值5。
- 首先,检查
通过这些示例,我们可以看到 get 方法根据传入的索引值来获取链表中相应位置的节点值,根据优化策略选择从头部或尾部开始遍历,以提高效率。
3)
public void addAtIndex(int index, int val) {
//index大于链表长度
if (index > size) {
return;
}
//index小于0
if (index < 0) {
index = 0;
}
size++;
//找到前驱
ListNode pre = this.head;
for (int i = 0; i < index; i++) {
pre = pre.next;
}
//新建结点
ListNode newNode = new ListNode(val);
newNode.next = pre.next;
pre.next.prev = newNode;
newNode.prev = pre;
pre.next = newNode;
}
这段代码是 MyLinkedList 类中的 addAtIndex(int index, int val) 方法,用于在链表的指定位置 index 处插入一个新节点,并将新节点的值设置为 val。让我逐行解释这段代码的功能:
-
if (index > size):这一行代码首先检查index是否大于链表的长度size,如果大于链表长度,表示要插入的位置在链表末尾之后,此时直接返回,不执行任何插入操作。 -
if (index < 0):这一行代码检查index是否小于 0,如果小于 0,表示要插入的位置在链表头部之前,将index设为 0,以确保在链表头部插入。 -
size++;:在插入节点之前,先增加链表的size,表示链表中的元素数量增加了一个。 -
找到前驱节点:
ListNode pre = this.head;:创建一个名为pre的节点引用,并将其初始化为链表的虚拟头结点head,准备从头部开始遍历找到插入位置的前驱节点。for (int i = 0; i < index; i++) { pre = pre.next; }:通过循环遍历链表,将pre移动到指定位置index的前驱节点。循环次数为index,每次移动pre到下一个节点,直到找到前驱节点。
-
新建结点:
ListNode newNode = new ListNode(val);:创建一个新的节点newNode,并将其值初始化为传入的参数val,准备将其插入到链表中。newNode.next = pre.next;:将新节点newNode的next指向前驱节点pre的next,从而连接新节点到链表中原来在index位置的节点之后。pre.next.prev = newNode;:将前驱节点pre的next节点的prev指向新节点newNode,以建立双向链接,使新节点成为前驱节点pre的下一个节点的前驱。newNode.prev = pre;:将新节点newNode的prev指向前驱节点pre,建立新节点和前驱节点之间的双向链接。pre.next = newNode;:最后,将前驱节点pre的next指向新节点newNode,完成插入操作。
通过这些步骤,新节点 newNode 成功地插入到了链表的指定位置 index 处,同时更新了链表的 size 和前驱节点的链接,实现了链表的插入操作。
例子
初始链表: 1 -> 2 -> 3
现在,让我们使用 addAtIndex 方法在不同位置插入新节点。
-
在索引为
1的位置插入值为4的新节点:myLinkedList.addAtIndex(1, 4);-
if (index > size):在这个例子中,index为1,小于链表的长度size(长度为3),所以不会进入这个条件。 -
if (index < 0):同样,index不小于0,所以不会进入这个条件。 -
size++;:链表的size增加了,变为4。 -
找到前驱节点:
ListNode pre = this.head;:pre初始化为虚拟头结点head。for (int i = 0; i < index; i++) { pre = pre.next; }:通过循环将pre移动到指定位置index的前驱节点,即节点1。
-
新建结点:
ListNode newNode = new ListNode(4);:创建新节点newNode,其值为4。newNode.next = pre.next;:将新节点newNode的next指向前驱节点pre的next,即节点2。pre.next.prev = newNode;:将前驱节点pre的next节点2的prev指向新节点newNode,建立双向链接。newNode.prev = pre;:将新节点newNode的prev指向前驱节点pre,建立双向链接。pre.next = newNode;:将前驱节点pre的next指向新节点newNode,完成插入操作。
最终链表变为:1 -> 4 -> 2 -> 3
-
-
在索引为
0的位置插入值为0的新节点:myLinkedList.addAtIndex(0, 0);-
由于
index等于0,不会进入if (index > size)和if (index < 0)条件。 -
size++;:链表的size再次增加,变为5。 -
找到前驱节点:
ListNode pre = this.head;:pre仍然初始化为虚拟头结点head。for (int i = 0; i < index; i++) { pre = pre.next; }:pre不需要移动,因为index已经是0。
-
新建结点:
ListNode newNode = new ListNode(0);:创建新节点newNode,其值为0。newNode.next = pre.next;:将新节点newNode的next指向前驱节点pre的next,即节点1。pre.next.prev = newNode;:将前驱节点pre的next节点1的prev指向新节点newNode,建立双向链接。newNode.prev = pre;:将新节点newNode的prev指向前驱节点pre,建立双向链接。pre.next = newNode;:将前驱节点pre的next指向新节点newNode,完成插入操作。
最终链表变为:0 -> 1 -> 4 -> 2 -> 3
-
通过这些示例,我们可以看到 addAtIndex 方法根据传入的索引值在链表中插入新节点,根据索引的位置将新节点连接到相应的前驱节点和后继节点,实现了链表的插入操作。
4)
public void deleteAtIndex(int index) {
//判断索引是否有效
if(index<0 || index>=size){
return;
}
//删除操作
size--;
ListNode pre = this.head;
for(int i=0; i<index; i++){
pre = pre.next;
}
pre.next.next.prev = pre;
pre.next = pre.next.next;
}
这段代码是 MyLinkedList 类中的 deleteAtIndex(int index) 方法,用于删除链表中指定位置 index 处的节点。让我逐行解释这段代码的功能:
-
if(index<0 || index>=size):这一行代码首先检查传入的index是否有效,即是否在链表的合法范围内。如果index小于 0 或大于等于链表的长度size,表示要删除的位置不合法,直接返回,不执行任何删除操作。 -
删除操作:
size--;:在删除节点之前,先减少链表的size,表示链表中的元素数量减少了一个。
-
找到前驱节点:
ListNode pre = this.head;:创建一个名为pre的节点引用,并将其初始化为链表的虚拟头结点head,准备从头部开始遍历找到要删除节点的前驱节点。for(int i=0; i<index; i++) { pre = pre.next; }:通过循环将pre移动到指定位置index的前驱节点。循环次数为index,每次移动pre到下一个节点,直到找到前驱节点。
-
删除节点:
pre.next.next.prev = pre;:将前驱节点pre的下一个节点的下一个节点(要删除的节点的下一个节点)的prev指向前驱节点pre,这一步是为了维护双向链接,将删除节点的下一个节点的前驱更新为前驱节点。pre.next = pre.next.next;:将前驱节点pre的next指向删除节点的下一个节点,从而完成删除操作。
通过这些步骤,链表成功地删除了指定位置 index 处的节点,同时更新了链表的 size 和前驱节点的链接,实现了链表的删除操作。如果 index 无效,则不执行任何删除操作。
举例
初始链表: 0 -> 1 -> 2 -> 3 -> 4
现在,让我们使用 deleteAtIndex 方法在不同位置删除节点。
-
删除索引为
2处的节点:javaCopy codemyLinkedList.deleteAtIndex(2);-
if(index<0 || index>=size):在这个例子中,index为2,在合法范围内(长度为5),所以不会进入这个条件。 -
删除操作:
size--;:链表的size减少为4。
-
找到前驱节点:
ListNode pre = this.head;:pre初始化为虚拟头结点head。for(int i=0; i<index; i++) { pre = pre.next; }:通过循环将pre移动到指定位置2的前驱节点,即节点1。
-
删除节点:
pre.next.next.prev = pre;:将前驱节点pre的下一个节点的下一个节点(要删除的节点的下一个节点3)的prev指向前驱节点pre,维护双向链接。pre.next = pre.next.next;:将前驱节点pre的next指向要删除节点的下一个节点,从而完成删除操作。
最终链表变为:0 -> 1 -> 3 -> 4
-
-
删除索引为
0处的节点:javaCopy codemyLinkedList.deleteAtIndex(0);-
同样,不会进入
if(index<0 || index>=size)条件,因为index为0,在合法范围内。 -
删除操作:
size--;:链表的size减少为3。
-
找到前驱节点:
ListNode pre = this.head;:pre初始化为虚拟头结点head。for(int i=0; i<index; i++) { pre = pre.next; }:pre不需要移动,因为index已经是0。
-
删除节点:
pre.next.next.prev = pre;:将前驱节点pre的下一个节点的下一个节点(要删除的节点的下一个节点1)的prev指向前驱节点pre,维护双向链接。pre.next = pre.next.next;:将前驱节点pre的next指向要删除节点的下一个节点,从而完成删除操作。
最终链表变为:1 -> 3 -> 4
-
通过这些示例,我们可以看到 deleteAtIndex 方法根据传入的索引值删除链表中的节点,同时更新了链表的 size 和前驱节点的链接,实现了链表的删除操作。