删除链表的倒数第N个结点(中等)
Q:给你一个链表,删除链表的倒数第 n
个结点,并且返回链表的头结点。
示例:
示例一: 输入:head = [1,2,3,4,5], n = 2 输出:[1,2,3,5]
示例二: 输入:head = [1], n = 1 输出:[]
实例三: 输入:head = [1,2], n = 1 输出:[1]
A:思路:对于本题来讲,其本质仍为删除链表中的某个结点,而想要删除链表中的结点,则一般需要找到待删除结点的上一个结点,然后改变next指针即可。
该题的需求是删除倒数第n个结点,那么我们的任务就分为了两步,第一步是找到待删除结点的上一个结点(为了方便删除),第二是更改next指针指向即可。很显然,该题的难点在于:如何去找到倒数第n个结点的上一个结点呢。归根结底,我们其实需要找的是倒数第 n + 1 个结点(第一步),而删除的是倒数第 n 个结点(第二步)。
对于链表这一类的题来讲,我们最好都去创建一个虚拟哑结点去指向头结点,这么做的目的就是:插入或删除链表中的任何结点都是很方便的。我们知道,在链表中头和尾是比较特殊的,一个没有前驱,一个没有后继,尤其是操作头结点时,若有了虚拟哑结点那就相当方便和简洁了。接下来开始解题,分两步走:
第一步:找到第 n + 1 个结点(利用快慢双指针)
定义两个快慢指针slow和fast,起始均指向dummyNode,随后为了使得后续同时移动快慢指针时,使得二者之间的距离恒为 n + 1 ,故先让fast移动 n + 1次。
即:
while(n != 0 && fast != null){ // while循环中移动了n次 fast = fast.next; --n; } fast = fast.next; // 这里再移动一次,fast一共移动了n + 1次
此时,fast指向了正数第 n + 2个结点,但是我们要找的是倒数第 n + 1个结点。现在哑结点和fast指针所指结点之间是一共有 n 个结点的,随后,当fast != null时,同时移动fast和slow,当fast指向null时,slow正好指向了倒数第 n + 1个结点。
即:
while(fast != null){ // 当fast不为空时,同时移动slow和fast fast = fast.next; // fast最终指向了null slow = slow.next; // slow最终指向了待删除节点的上一个结点,即倒数 n + 1 个结点 }
第二步:删除的是倒数第 n 个结点
当找到倒数第 n + 1个结点后,删除就非常简单了,利用 slow.next = slow.next.next 即可。
这里也要注意一点的就是,删除结点对于不用的编程语言来讲,并非是slow.next = slow.next.next 就万事大吉了。这只是在逻辑上的删除,而在物理即内存中并不一定就被删除掉了。对于Java语言来说,我们并不需要做多余的操作,垃圾回收器gc会帮我们去回收该结点并释放内存;但对于C++来讲,就需要手动的去删除和释放内存空间了,毕竟偏底层的高级语言可以较为直接的去操作内存,即是优点但也可能是个缺点,所以对于C++来说,逻辑上删除后,还需要物理上的手动删除,即最后的删除操作应为① ListNode temp = slow.next; ②slow.next = slow.next.next; ③ delete(temp);
以下是Java代码,仅供参考:
/** * 这里是链表结构的定义,面试中也有可能考察,需要会写出。 * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode() {} * ListNode(int val) { this.val = val; } * ListNode(int val, ListNode next) { this.val = val; this.next = next; } * } */ class Solution { public ListNode removeNthFromEnd(ListNode head, int n) { ListNode dummyNode = new ListNode(0, head); // 1. 定义一个哑结点,并初始指向头结点 ListNode fast = dummyNode, slow = dummyNode; // 2. 分别定义一个快指针fast和慢指针slow均初始指向哑结点 while(n != 0 && fast != null){ // 3. 在保证fast不为空指针的情况下将其向前挪动 n 次 fast = fast.next; --n; } fast = fast.next; // 4. fast往后多移动一位,为了使得后面slow和fast一起移动时,slow最终可以指向待删除节点的上一个节点,也是为了删除方便 while(fast != null){ // 5. 当fast不为空时,同时移动slow和fast fast = fast.next; // 6. fast最终指向了null slow = slow.next; // 7. slow最终指向了待删除节点的上一个结点,即倒数 n + 1 个结点处 } slow.next = slow.next.next; // 8. 更改slow的next指针,以删除倒数第 n 个结点 return dummyNode.next; // 9. 返回哑结点的下一个结点,即head结点 } }