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
和前驱节点的链接,实现了链表的删除操作。