链表也是面试常见题型,面试中常见单向链表,其可以定义为如下形式:
public class ListNode {
public int val;
public ListNode next;
public ListNode(int val) { this.val = val; }
}
哨兵节点是为了简化处理链表边界条件而引入的附加链表节点。哨兵节点通常位于链表的头部,它的值没有任何意义。在一个有哨兵节点的链表中,从第2个节点开始才真正保存有意义的信息。
这道题用到了双指针思想中的前后双指针。简单来说就是先让第1个指针移动k-1步,然后再如第2个指针指向链头节点,再让两个指针以相同速度一起移动。当第1个指针到达链尾节点时第2个指针正好指向倒数第k个节点。
题目要求只遍历链表一次,如果没有这个要求我们可以先遍历一遍链表得出链表长度,然后在第2次遍历时找出第n-k个节点(即倒数第k+1个节点),然后将该节点的next指针指向倒数第k-1个节点。由此,我们可以得知该题关键是如何在遍历一次链表的情况下找到这个倒数第k+1个节点。这时就可以利用上前后双指针思想。详细代码如下:
public ListNode removeNthFromEnd(ListNode head, int n) {
// 创建一个哨兵节点,简化处理头节点删除的情况
ListNode dummy = new ListNode(0);
dummy.next = head;
// 初始化两个指针,front 指向链表头,back 指向哨兵节点
ListNode front = head, back = dummy;
// 让 front 指针先移动 n 步,使其指向第 n+1 个节点
for (int i = 0; i < n; i++) {
front = front.next;
}
// 同时移动 front 和 back 指针,直到front指针为空
// 循环结束 back 指针指向倒数第 k+1 个节点
while (front != null) {
front = front.next;
back = back.next;
}
// 删除倒数第 n 个节点,将 back 指针的 next 指向下下个节点
back.next = back.next.next;
// 返回新的链表头
return dummy.next;
}