方法一、计算链表的长度
class Solution {
public:
int getLength(ListNode* head){
int length=0;
while(head!=nullptr){
++length;
head=head->next;
}
return length;
}
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* dummyHead=new ListNode(0); //使用虚拟头结点,这样方面处理删除实际头结点的逻辑
dummyHead->next=head; //令虚拟节点的next指向链表的头部
ListNode* cur=dummyHead;
int length=getLength(head); //求得原始链表的长度,不包括dummyHead节点
for(int i=0;i<length-n;++i){ //假设链表为1,2,3,4,5删除倒数第二个节点(4)令指向虚拟头节点的cur向后移动length-n=5-2=3(i=0,1,2)次,这样就使得cur此时在要删除的节点的前一个位置
cur=cur->next;
}
ListNode* temp=cur->next; //记录要删掉的节点,方便释放该节点的空间
cur->next=cur->next->next;
delete temp;
return dummyHead->next;
}
};
复杂度分析
-
时间复杂度:O(N),其中 N 是链表的长度。
-
空间复杂度:O(1)。
方法二、使用栈
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* dummy=new ListNode(0,head); //虚拟头节点
stack<ListNode*> stk; //存放链表节点的栈
ListNode* cur=dummy;
while(cur!=nullptr){ //包括dummy在内,将所有节点依次压入栈中
stk.push(cur);
cur=cur->next;
}
for(int i=0;i<n;++i){ //由于删除倒数第n个节点,我们就将栈顶的前n个节点删除,最后栈顶节点就是要删除的节点的前一个节点
stk.pop();
}
ListNode* pre=stk.top();
ListNode* temp=pre->next;
pre->next=pre->next->next;
delete temp;
return dummy->next;
}
};
复杂度分析
-
时间复杂度:O(N),其中 N 是链表的长度。
-
空间复杂度:O(N),其中 N 是链表的长度。主要为栈的开销。
方法三、双指针(快慢指针)
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* dummy=new ListNode(0,head); //虚拟头节点
ListNode* fast=dummy;
ListNode* slow=dummy;
for(int i=0;i<n+1;++i){ //快指针先从dummy节点走n+1步
fast=fast->next;
}
while(fast!=nullptr){ //快慢指针一起前进,知道fast为空
slow=slow->next;
fast=fast->next;
}
//while结束之后slow指向要删除的节点的前一个节点
ListNode* temp=slow->next;
slow->next=slow->next->next;
delete temp;
return dummy->next;
}
};
-
时间复杂度:O(N),其中 N 是链表的长度。
-
空间复杂度:O(1)。