「这是我参与2022首次更文挑战的第9天,活动详情查看:2022首次更文挑战」
删除链表的倒数第N个结点
LeetCode传送门19. 删除链表的倒数第 N 个结点
原题
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
Given the head of a linked list, remove the node from the end of the list and return its head.
Example:
Input: head = [1,2,3,4,5], n = 2
Output: [1,2,3,5]

Input: head = [1], n = 1
Output: []
Input: head = [1,2], n = 1
Output: [1]
Constraints:
- The number of nodes in the list is
sz. - 1 <= sz <= 30
- 0 <= Node.val <= 100
- 1 <= n <= sz
思考线
解题思路
看到这道题,我们首先思考怎么样找到倒数第n个结点呢?
在这里我们假设链表的总长度为 L。
若我们先设一个快指针fast走到n个结点的地方,剩下的长度就是L-n。
若我们此时在head处再设一个慢指针slow,然后让快慢指针一起走,那么快指针走向null的时候,说明慢指针正好在倒数第n个点,而这正是我们想删除的节点。 知道了要删除的点,想找到删除点的前一个是非常容易的,我们只需要让删除节点的前一个指向需要删除节点的下一个即可。
现在删除节点的问题解决了,那么还有一个问题。做完删除操作后我们要返回新链表的头结点。那么我们该怎么办呢?
这里面分为两种情况
- 头结点被删除了,我们返回第二个节点。
- 头结点没有被删除,我们返回原头结点即可。
我们当然可以算出 list 的长度,然后根据 n和长度的关系判断是否是头结点。不过这么做肯定不优雅。
于是乎我想到了我们的老朋友dummyHead.若设置了dummyHead,我们的头部肯定就是dummyHead的next节点啦。
若没看之前文章的朋友,我解释一下,
dummyHead就是我们人为给链表添加的一个虚拟头结点,这样在某些情况下比较方便计算和去除特殊情况
那么接下来我们写代码就顺水推舟了。
/**
* Definition for singly-linked list.
* class ListNode {
* val: number
* next: ListNode | null
* constructor(val?: number, next?: ListNode | null) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
* }
*/
function removeNthFromEnd(head: ListNode | null, n: number): ListNode | null {
const dummyHead = new ListNode(0);
dummyHead.next = head; // 为了不考虑删除头结点的情况,我们增加一个虚拟头结点
let fast = dummyHead;
let slow = dummyHead;
while(n--) { // 走n步,到达dummyHead后的第n+1个结点,也就是head后的第n个结点
fast = fast.next;
}
while(fast.next) { // 同时走,若fast.next === null的时候,证明slow走到了 倒数第n+1个结点。
slow = slow.next;
fast = fast.next;
}
slow.next = slow.next.next; // 删除倒数第n个结点
return dummyHead.next; // 返回头结点
};
时间复杂度
O(n): n为链表的长度,因为我们的fast指针要从头走到尾,所以时间复杂度为n。
用经典算法结构来解题
除了上面的方法来获取倒数第n个链表结点外,我们还能怎么办呢?先进后出的办法最容易找到倒数第n个结点了。于是乎我们就瞬间明白了,使用stack来保存结点,然后再执行出栈操作n次即可得到倒数第n个结点的前一个节点,而我们也可以很容易根据栈内元素是否为n来确定删除的是不是原始头结点。
我们可以这样实现上面的想法
function removeNthFromEnd(head: ListNode | null, n: number): ListNode | null {
const stack = [];
let point = head;
while(point) {
stack.push(point);
point = point.next;
}
const isHeadChange = stack.length === n;
if(stack.length === n) {
return head.next;
}
while(n--) {
stack.pop()
}
const pre = stack.pop();
pre.next = pre.next.next;
return head;
};
时间复杂度
O(n): 因为我们的栈中保存了链表中所有的节点,所有时间复杂度为n.
这就是我对本题的解法,如果有疑问或者更好的解答方式,欢迎留言互动。