题目描述:
给你一个链表,删除链表的倒数第
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]
第一眼看到这个题目,能够想到的最直接的方法就是先利用循环计算整个链表的长度size,然后用size减去n,再根据差值找到要删除的元素然后进行删除操作即可,通过使用虚拟头节点能够使删除操作统一,不然需要额外考虑删除头节点时的特殊情况。代码如下:
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
int size = 0;
//引入虚拟头节点,不然需要考虑删除头节点的特殊情况。
ListNode dummyNode = new ListNode(-1,head);
ListNode cur = dummyNode;
//计算整个链表长度
while(cur!=null){
size++;
cur = cur.next;
}
cur = dummyNode;
//index表示被删除节点的前一个节点的位置
int index = size - n-1;
while(index!=0){
cur = cur.next;
index -- ;
}
cur.next = cur.next.next;
return dummyNode.next;
}
}
执行结果如下:
除了这种暴力解法,还可以利用双指针法优化。
双指针法
按照双指针法的思路,需要一个快指针fast和一个慢指针slow。如果要删除倒数第n个节点,让fast移动n步,然后让fast和slow同时移动,直到fast指向链表末尾。删掉slow所指向的节点就可以了。
这里依然使用虚拟头节点的技巧,方便处理头节点的删除逻辑。代码如下:
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummy = new ListNode(-1);
dummy.next = head;
ListNode slow = dummy;
ListNode fast = dummy;
while (n-- > 0) {
fast = fast.next;
}
// 记住 待删除节点slow 的上一节点
ListNode prev = null;
while (fast != null) {
prev = slow;
slow = slow.next;
fast = fast.next;
}
// 上一节点的next指针绕过 待删除节点slow 直接指向slow的下一节点
prev.next = slow.next;
// 释放 待删除节点slow 的next指针, 这句删掉也能AC
slow.next = null;
return dummy.next;
}
执行结果如下:
通过比较两种解法的执行结果,我们发现暴力解法的内存消耗还小于双指针法。但是这种差距并不大。正如Carl神所说,不需要过度迷信Leetcode的执行结果,只要我们自己能够计算时间和空间复杂度并将代码调整至最佳即可。