方法一:两次遍历算法
思路
我们注意到这个问题可以容易地简化成另一个问题:删除从列表开头数起的第 (L - n + 1)个结点,其中 L是列表的长度。只要我们找到列表的长度 L,这个问题就很容易解决。
算法
首先我们将添加一个哑结点作为辅助,该结点位于列表头部。哑结点用来简化某些极端情况,例如列表中只含有一个结点,或需要删除列表的头部。在第一次遍历中,我们找出列表的长度 L。然后设置一个指向哑结点的指针,并移动它遍历列表,直至它到达第 (L - n)个结点那里。我们把第 (L - n)个结点的 next 指针重新链接至第 (L - n + 2)个结点,完成这个算法。
/**
* 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 *p = head; //定义指针P指向头节点,用于第一次遍历链表
ListNode *pre = head; //定义指针pre指向头结点,用于第二次遍历链表
int len = 0; //链表长度初始化
//第一次遍历链表求链表长度
while(p->next != NULL){
len ++;
p = p->next;//p指针后移一位
}
//第二次遍历链表,找到第(len-n)个节点,要删除的是第(len-n+1)个节点
for(int i=0;i<(len-n);i++){
pre = pre->next;
}//跳出for循环后pre指向的是要删除节点的前面一个节点,第(len-n)个节点
pre->next = pre->next->next; //让第(len-n)个节点指向第(Len-n+2)个节点,即删除了第(len-n+1)个节点
return head;
}
};
指向节点的指针变量类型也是节点!
两次遍历链表的方法较复杂,题目要求是遍历依次链表完成。
方法二:一次遍历算法:双指针
step 1:创建一个虚拟头结点,指向链表头节点。目的是在头结点有可能被删除的情况省去判断步骤;
step 2:创建两个指针first 和second,开始时都指向头节点。先让first指针后移n次,再让first指针和second每次同时后移一个节点,直到first指针指向最后一个节点,此时second指针正好指向要删除节点的前面一个节点。
step 3:创建新的节点链接关系,删除倒数第n个节点
视频学习: https://www.bilibili.com/video/av60198891?from=search&seid=2174977360875970213
C++实现:
/**
* 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) {//一次遍历
//创建虚拟头节点,指向链表头节点
auto dummy = new ListNode(-1);
//ListNode * dummy = new ListNode(-1); //auto方法自动判定类型
dummy->next = head;
//定义两个指针,指向虚拟头结点
auto first = dummy;
auto second = dummy;
//ListNode *first = dummy;
//ListNode *second = dummy;
//先让first指针后移n次
while(n--){
first = first->next;
}
//再让first和second同时后移,直到first指向链表最后一个节点
while(first->next != NULL){
first = first->next; second = second->next;
}
//此时second正好指向倒数第n+1个节点,删除倒数第n个节点
second->next = second->next->next;
return dummy->next;//这里不能返回head,因为头节点有可能被删除
}
};
加入虚拟头结点的做法不用再判断这个链表可能为空的情况。