题目
给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。
示例:
给定一个链表:1->2->3->4->5 n = 2
当删除了倒数第二个节点后,链表变为1->2->3->5
说明:
给定的n保证是有效的。
进阶:
你能尝试使用一趟扫描实现吗?
解题过程
直觉做法
遍历一遍链表,得到链表的长度len。再次遍历删除倒数第n个节点就是删除从列表开头的(len-n+1)的节点。即两次遍历方法。
(看了官方题解才改正代码,没考虑到下面的情况)
有两种极端情况是,列表只有一个节点,或者删除第一个节点。
因此官方题解是添加哑节点辅助,该节点位于列表头部。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
// 创建一个哑节点指向第一个节点
ListNode * dummy = new ListNode(0);
dummy->next = head;
// 遍历得到链表长度length
int length = 0;
ListNode * first = head;
while(first != NULL){
length++;
first = first->next;
}
//删除从列表头开始第(length-n+1)的节点
length -= n;
first = dummy;
while(length > 0){
length--;
first = first->next;
}
first->next = first->next->next;
return dummy->next;
}
};
时间复杂度:O(L),第一次遍历L次得到列表长度L,第二次遍历L-n次,操作执行2L-n次。
空间复杂度:O(1)
优化
遍历了两遍链表才成功,是否可以一遍呢?
我们可以使用两个指针,让它们保持n个节点的距离,当第一个指针到达NULL时,第二个指针刚好到达倒数第n个节点。这里只遍历了一次,即一次遍历法。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
// 创建一个哑节点指向第一个节点
ListNode * dummy = new ListNode(0);
dummy->next = head;
ListNode * slow = dummy;
ListNode * fast = dummy;
// 让两个指针保持n+1个节点的距离,因为我们要的是待删除节点的前一个节点的指针
for(int i = 1; i <= n+1; i++){
fast = fast->next;
}
while(fast != NULL){
fast = fast->next;
slow = slow->next;
}
slow->next = slow->next->next;
return dummy->next;
}
};
时间复杂度:O(L),只需要遍历一次列表。
空间复杂度:O(1)。