19. 删除链表的倒数第 N 个结点
题目
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
示例 1:
输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]
示例 2:
输入:head = [1], n = 1
输出:[]
示例 3:
输入:head = [1,2], n = 1
输出:[1]
提示:
链表中结点的数目为 sz
1 <= sz <= 30
0 <= Node.val <= 100
1 <= n <= sz
1、快慢指针
这题做烂了,快指针先走n步,然后快慢一起走到最后一个节点fast.next != None
(注意不是走到空!不然slow会指向待删除结点),则最后slow会指向待删除结点的前驱,就可以删了
注意点:链表删除问题,加头结点哈
时间复杂度: O ( L ) O(L) O(L)
空间复杂度: O ( 1 ) O(1) O(1)
python
class Solution:
def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]:
dummyHead = ListNode()
dummyHead.next = head
slow, fast = dummyHead, dummyHead
while n != 0:
fast = fast.next
n -= 1
while fast.next != None : # 让slow最终指向待删除结点的前一个结点
slow = slow.next
fast = fast.next
slow.next = slow.next.next
return dummyHead.next
c++
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
// 注意slow最终指向的是代数第n个结点的前驱!!
if(head == NULL || head->next == NULL)
return head;
ListNode* dummyNode = new ListNode(-1);
dummyNode->next = head;
ListNode* slow = dummyNode;
ListNode* fast = dummyNode;
while(n-- != 0) //移动n次
fast = fast->next;
while(fast->next && slow->next){
slow = slow->next;
fast = fast->next;
}
slow->next = slow->next->next;
return dummyNode->next;
}
};
一种错误写法
典型错误哈,当链表只有一个元素[1]经过内层while,fast已经指向1,再经过slow = slow.next;fast = fast.next, fast = None了,但是却处于最外层while的内部,而最外层条件是fast.next != None,明显不符合,最后slow指向1,也表明这么做是错误的。因此必须老老实实分两步写
class Solution:
def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]:
dummyHead = ListNode()
dummyHead.next = head
slow, fast = dummyHead, dummyHead
while fast.next != None : # 让slow最终指向待删除结点的前一个结点
while n != 0:
fast = fast.next
n -= 1
slow = slow.next
fast = fast.next
slow.next = slow.next.next
return dummyHead.next
2、递归:c++
方法是leetcode 203/王道01,02【链表递归删除操作】,有两种递归写法
注意:n都要作为引用传入!
时间复杂度: O ( L ) O(L) O(L)
空间复杂度: O ( L ) O(L) O(L)
2.1 无返回值递归写法:删除工作手动进行
这里改成了带头结点的做法,不带头结点就把所有的dummyNode->next换成head即可
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
// 注意slow最终指向的是代数第n个结点的前驱!!
if(head == NULL || head->next == NULL)
return head;
ListNode* dummyNode = new ListNode(-1);
dummyNode->next = head;
func(dummyNode, n);
return dummyNode->next;
}
void func(ListNode* dummyNode, int &n){
//递归到最后,再机算弹出次数,仍然是链表递归的问题
if(dummyNode->next == NULL)
return ;
func(dummyNode->next, n);
n -= 1; // 形参内传入n的引用,才是在原n不断-1;比如n=2,则2-1=1,然后1-1 = 0;如果不传引用,每次都是在2-1;2-1;2-1;也就是不是引用的话相当于创建了n的副本
if(n == 0)
dummyNode->next = dummyNode->next->next;
return ;
}
};
2.2 有返回值写法:制定连接规则
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
// 注意slow最终指向的是代数第n个结点的前驱!!
return func(head, n);
}
ListNode* func(ListNode* head, int &n){
/**栈弹出过程
func(5)
5->next = func(NULL) = NULL
2-1 = 1 retrun 5->NULL
func(4)
4->next = func(5) = 5->NULL
1-1 = 0 return 4->next = 5->NULL
func(3)
3->next = func(4) = 5->NULL
return 3->5->NULL
。。。。。
**/
if(head == NULL)
return head;
//先递归到最尾部
head->next = func(head->next, n);
n -= 1;
if(n==0) //当前结点head要删除,则返回head->next作为下一次head->next的连接内容
return head->next;
return head; //正常的每次连接方式
}
};