一、获取第N个节点的数值
在链表中,尤其是单链表,头节点的处理有时会带来额外的复杂性。比如,在链表为空的情况下,想要访问头节点或者在其前面插入新的节点时,就需要特别小心地处理边界情况。为了避免这类复杂性,常常会在链表的头部添加一个虚拟的头节点(也叫作哑节点或哨兵节点),这个节点并不存储实际的数据,仅用于简化算法逻辑。
通过引入虚拟头节点 `dummyHead`,可以统一处理链表中的节点操作,而不需要针对头节点进行特判。例如,当我们要获取链表的头节点的值时,可以使用 `dummyHead.next.val` 来代替直接操作 `head.val`。
这样做有几个好处:
1. **简化插入操作**:在链表的开头插入新节点时,只需要修改 `dummyHead.next` 的指向即可,而不需要单独处理头节点的情况。
2. **简化删除操作**:删除头节点时,同样只需改变 `dummyHead.next` 的指向,而不需要单独编写处理头节点的代码。
3. **统一迭代逻辑**:在遍历链表时,可以统一迭代逻辑,不需要特判是否是头节点。
以下是一个简化的示例,展示如何通过虚拟头节点来访问头节点的值:
在这个例子中,`dummyHead` 是一个虚拟的头节点,它指向真正的头节点。当链表为空时,`dummyHead.next` 为 `null`,此时尝试获取头节点的值将抛出异常。
public int get(int index) {
if(index<0 || index >= size){
return -1;
}
ListNode dummy=new ListNode();//设置虚拟节点
dummy.next=head;
for(int i=0;i<=index+1;i++){
dummy=dummy.next;//通过循环遍历找到需要节点的数值
}
return dummy.val;
}
二、在头部插入节点
public void addAtHead(int val) {
ListNode newnode = new ListNode(val);//创建新的节点
newnode.next=head.next;
head.next=newnode;//在头部插入
size++;
}
三、在尾部插入节点
public void addAtTail(int val) {
ListNode newNode = new ListNode(val);
ListNode cur = head;
while(cur.next != null){
cur=cur.next;
}
cur.next=newNode;
size++;
}
四、在第N个节点前插入节点
1. 参数检查
- 检查索引是否超出链表长度:如果提供的索引
index
大于链表的当前大小size
,那么直接返回,不做任何插入操作。这是因为索引超出了链表的范围。 - 处理负索引:如果提供的索引
index
小于0,那么将其设置为0。这相当于把新节点插入到链表的头部。
2. 修改链表大小
- 更新链表大小:在实际插入节点之前,链表的大小
size
增加1。这是因为无论插入操作成功与否,链表的实际元素数量都会增加一个。
3. 定位前驱节点
- 定位前驱节点:创建一个名为
pred
的临时变量来追踪当前索引的前驱节点。通过循环遍历链表,每次迭代都将pred
向前移动一步,直到达到目标索引index
的前一个节点。
4. 插入新节点
- 创建新节点:创建一个新的
ListNode
实例adds
,并将其值设置为val
。 - 调整指针:新节点
adds
的next
指针指向当前pred
节点的下一个节点。 - 插入节点:更新
pred
节点的next
指针,使其指向新创建的节点adds
。这样就完成了在索引index
处插入新节点的操作。
代码流程实例
假设链表当前的状态如下:
head -> 1 -> 2 -> 3 -> null
链表的大小 size
为 3。
如果调用 addAtIndex(1, 5)
,即在索引 1
处插入值为 5
的节点:
- 检查
index
是否大于size
(3),不是; - 检查
index
是否小于0
,不是; - 增加
size
到 4; - 从
head
开始,迭代i
次(这里是1
次),找到前驱节点pred
,即head
的下一个节点1
; - 创建新节点
adds
,其值为5
; adds.next
指向pred.next
,也就是2
;pred.next
指向adds
;
最终链表变为:
head -> 1 -> 5 -> 2 -> 3 -> null
通过这种方式,可以在单链表的任意位置插入新节点,并且通过虚拟头节点 head
的使用,可以简化插入操作的代码逻辑。
代码:
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 adds=new ListNode(val);
adds.next=pred.next;
pred.next=adds;
}
五、删除某个节点的代码
public void deleteAtIndex(int index) {
if (index < 0 || index >= size) {
return;
}
size--;
//因为有虚拟头节点,所以不用对Index=0的情况进行特殊处理
ListNode pred = head;
for (int i = 0; i < index; i++) {
pred = pred.next;
}
pred.next = pred.next.next;
}
这种方法不仅简化了代码,而且提高了代码的健壮性和可读性。使用虚拟头节点的方式非常适合那些需要频繁处理头节点场景的应用程序。