19. Remove Nth Node From End of List(删除链表的倒数第N个节点)
1. 题目描述
给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。
示例:
给定一个链表: 1->2->3->4->5, 和 n = 2.
当删除了倒数第二个节点后,链表变为 1->2->3->5.
说明:
给定的 n 保证是有效的。
进阶:
你能尝试使用一趟扫描实现吗?
2. 两次遍历(Two pass algorithm)
2.1 解题思路
比较常规的思路是,先一次遍历链链表,统计一共有多少个节点,然后删除第(总结点数 - n + 1)个节点就行了,这是一种从前往后删除节点的思路,需要遍历链表两次。
这个思路的代码大家可以自己想想怎么写,如果不是很清楚可以参考下面3. 一次遍历的代码,大体的思路和删除节点的思路都是大同小异的。
3. 一次遍历(One pass algorithm)
3.1 解题思路
那么,根据上面的2. 两次遍历的思路,我们反过来考虑一下,能否从后往前删除节点了,这样就只需要遍历一次链表。首先,我们不知道给定链表一共有多少个节点,所以先找到最末节点与被删除节点之间的距离:n - 1,即删除节点移动多少,达到末尾节点。在查找被删除节点的过程中,这个距离是相对固定的,比如下面这个例子:
1 -> 2 -> 3 -> 4 -> 5, n = 3
开始,被删除节点(deletedNode)指向1,末尾节点(endOfLink)指向n - 1 = 2,即3这个节点,但这个末尾节点不是真正的末尾节点,我们知道真正的末尾节点是5,所以我们需要循环找到真正的末尾节点,所以每移动末尾节点一次,我们相应移动被删除节点一次。同样,我们在开始初始化一个前节点(pre) = nullptr,后节点(next) = 2,这两个节点也跟随末尾节点移动而移动。
所以被删除节点(deletedNode) = head, 末尾节点(endOfLink) = head,前节点(pre) = nullptr,后节点(next)= head->next。如果deletedNode 等于head,直接返回head->next;反之,pre->next = next,返回head即可。
大家可以参考一下下图的思路,加深理解:
3.2 实例代码
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
// if (!head) return nullptr; // 通过测试,题目不需要考虑空链表的情形
ListNode* endOfLink = head, * deletedNode = head, * pre = nullptr, *next = head->next;
for (int i = 0; i < n - 1; i++) endOfLink = endOfLink->next; // 固定需要删除节点与末尾节点的距离
// 一次遍历找到需要删除的节点
while (endOfLink->next) {
endOfLink = endOfLink->next;
deletedNode = deletedNode->next;
if (pre) pre = pre->next;
else pre = head; // 因为首次pre为空指针,需要重新赋值
next = next->next;
}
if (deletedNode == head) return head->next; // 如果需要删除的节点为head,直接返回head的下一个节点即可
pre->next = next;
return head;
}
};