题目描述:
给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。示例:
给定一个链表: 1->2->3->4->5, 和 n = 2.
当删除了倒数第二个节点后,链表变为 1->2->3->5.
在链表中删除一个结点,需要找到删除结点的前一个结点,当删除结点是头节点时,需要单独判断。本题的解题方法中引入,dummy哑结点的概念,dummy的next指向链表的头节点。这样以来就头节点也存在前结点了,不需要进行单独判断。
解题方法1 | 计算链表长度
解题思路:
- 计算链表的长度
- 遍历到需要删除结点的前一位
- 将前一位指向需要删除结点的下一位
时间复杂度:O(m+n)
空间复杂度:O(1)
参考代码:
int getLength(ListNode* head)
{
int length=0;
while(head)
{
length++;
head=head->next;
}
return length;
}
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* dummyNode=new ListNode(0,head);
ListNode* currNode=dummyNode;
int length=getLength(head);
for(int i=1;i<length-n+1;i++)
currNode=currNode->next;
currNode->next=currNode->next->next;
ListNode* ans=dummyNode->next;
delete dummyNode;
return ans;
}
解题方法2 | 栈
解题思路:
利用栈的“先进后出”原则
- 将所有元素入栈,包括哑结点
- 弹出n个元素
- 此时,栈顶元素是需要删除结点的前一个结点
时间复杂度:O(L)
空间复杂度:O(L)
参考代码:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* dummy=new ListNode(0,head);
ListNode* curr=dummy;
stack<ListNode*> stk;
while(curr)
{
stk.push(curr);
curr=curr->next;
}
for(int i=0;i<n;i++)
stk.pop();
ListNode* pre=stk.top();
pre->next=pre->next->next;
ListNode* ans=dummy->next;
delete dummy;
return ans;
}
解题方法3 | 双指针
解题思路:
- 快指针(fast) 指向head
- 慢指针(slow)指向dummy
- fast先移动n步长
- fast 和 slow 同时移动,直到fast==nullptr
时间复杂度:O(L)
空间复杂度:O(1)
参考代码:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* dummy=new ListNode(0,head);
ListNode* slow=dummy;
ListNode* fast=head;
for(int i=0;i<n;i++)
fast=fast->next;
while(fast)
{
fast=fast->next;
slow=slow->next;
}
slow->next=slow->next->next;
ListNode* ans=dummy->next;
delete dummy;
return ans;
}
node : 针对双指针,因该注意以下两个问题
- 在调用next之前,应确保结点不为nullptr
- 设定循环条件时,应注意避免无限循环
参考
[1] : 19. 删除链表的倒数第N个节点