1.题目要求
- 给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
- 进阶:你能尝试使用一趟扫描实现吗?
2.题目示例
- 示例 1:
输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]
输入:head = [1], n = 1
输出:[]
输入:head = [1,2], n = 1
输出:[1]
3.提示
- 链表中结点的数目为 sz
- 1 <= sz <= 30
- 0 <= Node.val <= 100
- 1 <= n <= sz
- Maintain two pointers and update one with a delay of n steps.
维护两个指针,并以n步的延迟更新一个指针。
4.解题
4.1 解题思路
- 读题后最容易想到的是先遍历链表,得到链表的长度L,然后再次遍历链表找到L-n+1就是我们要删除的节点(为了与题中的n同步,节点的下标就从1开始)
- 而后为了方便删除数据可以加入哑节点(哑节点是链表中指向第一个有数据节点的节点)。从哑节点开始,遍历到L-n+1的下一个节点就是需要删除的节点(加入哑节点可以避免对头节点的特殊情况做处理)
- 在熟悉这个思路的基础上,就可以稍微改良一下思路
- 我们可以在遍历链表的同时将所有的节点依次加入栈,栈有先进后出的原则,所以在对栈进行弹出的时候,弹出的第n个节点就是我们要删除的节点,并且当前栈顶的节点就是待删除节点的前驱节点
4.2 业务代码
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummy = new ListNode(0, head);
Deque<ListNode> stack = new LinkedList<ListNode>();
ListNode cur = dummy;
while (cur != null) {
stack.push(cur);
cur = cur.next;
}
for (int i = 0; i < n; ++i) {
stack.pop();
}
ListNode prev = stack.peek();
prev.next = prev.next.next;
ListNode ans = dummy.next;
return ans;
}
}
4.3 运行结果
5.优化
5.1 优化思路
- 换一个思路,由于是要找到倒数第n个节点,我们可以设置两个名为first和second的两个指针,同时对链表进行遍历,并且first要比second快n个节点,这样在first到达最末尾的时候,second正好到达需要删除的节点处
- 根据上一个方法的思路,我们可以考虑在开始的时候将second指向哑节点,这样一来当first到达末尾的时候,second到达的是删除节点的前一个节点。
5.2 优化业务代码
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummy = new ListNode(0, head);
ListNode first = head;
ListNode second = dummy;
for (int i = 0; i < n; ++i) {
first = first.next;
}
while (first != null) {
first = first.next;
second = second.next;
}
second.next = second.next.next;
ListNode ans = dummy.next;
return ans;
}
}
5.3 优化结果
6.总结