题目链接:https://leetcode-cn.com/problems/remove-nth-node-from-end-of-list/
阅读题目后,有两种思路。
两次遍历,我们首先用一个指针遍历链表,统计出链表的长度L,那么倒数第n个节点,就是正数的第( L - n + 1)个。然后我们另启用一个指针从头开始遍历到正数第(L - n)个节点,执行删除节点操作。(注:此方法需要考虑特殊情况,比如只有一个指针和要删除的指针是head指针所指的节点的时候)。
var removeNthFromEnd = function(head, n) {
// 如果链表长度为1
if(head.next === null) {
return null;
}
let count = 0; // 用于统计链表的长度
let p = head;
// 统计链表长度
while(p !== null) {
count++;
p = p.next;
}
// 如果删除的节点是head
if(count === n) {
head = head.next;
} else {
//下面可考虑普通情况, 我们新建一个指针q指向head开始遍历,遍历到链表正数的第(count - n)个
let q = head;
for(let i = 1; i < count - n ; i++) {
q = q.next;
}
// 执行删除操作
q.next = q.next.next;
}
return head;
};
上面的算法是需要考虑边界特殊情况的问题,那有没有什么方法优化呢?在主要思想相同的前提下,我们可以引入一个「哨兵节点」来消除上述特殊情况的考虑,可以用统一的操作来执行。
我们回想一下上一种方法中,我们要删除一个节点,则需要这个节点有「前继节点」,那么如果要删除的是head节点,则没有「前继节点」,head节点之后的节点都有「前继节点」,所以如果head节点也有「前继节点」,我们是不是就能统一操作了呢?故而我们创建一个「哨兵节点」指向head。注意:创建「哨兵节点」,相当于第0个节点,第二次遍历我们从「哨兵节点」开始,然后遍历到第(L - n)个,然后删除目标节点,返回的时候应该是「哨兵节点」的「后继节点」。
var removeNthFromEnd = function(head, n) {
let count = 0; // 用于统计链表长度
let p = head; // 用于第一次遍历
while(p !== null) {
count++;
p = p.next;
}
// 创建一个「哨兵节点」指向head
let k = new ListNode(0);
k.next = head;
let q = k;
// 删除的是第( count - n + 1 )个,我们还是要找到第( count - n )个
// 注意本次q的起点是那个「哨兵节点」,也就是第0个
for(let i = 0; i < count - n; i++) {
q = q.next;
}
// 执行删除操作
q.next = q.next.next;
return k.next;
};