链接
思路
1、双指针
链表中倒数第k个节点
可以使用两个指针 p 和 q 同时对链表进行遍历,并且 [t 比 q 快 n 个节点。当 p 遍历到链表的末尾时,q 恰好处于倒数第 n 个节点。
具体地,初始时 p 和 q 均指向头节点。首先使用 p 对链表进行遍历,遍历的次数为 n次。此时,p 和 q 之间间隔了 n−1 个节点,即 p 比 q 超前了 n 个节点。
然后,我们同时使用 p 和 q 对链表进行遍历。当 p 遍历到链表的末尾(即 p 为空指针)时,q 恰好指向倒数第 n 个节点。
初始时将 q 指向傀儡节点,这样一来,当 p 遍历到链表的末尾时,q 的下一个节点就是我们需要删除的节点。
2、计算链表长度
从头节点开始对链表进行一次遍历,得到链表的长度 length。随后我们再从头节点开始对链表进行一次遍历,当遍历到第 length-n 个节点时,它就是我们需要删除的节点。
为了方便删除操作,我们可以从傀儡节点开始遍历length−n 个节点。当遍历到第 length-n 个节点时,它的下一个节点就是我们需要删除的节点。
3、栈
在遍历链表的同时将所有节点依次入栈。根据栈“先进后出“的原则,我们弹出栈的第 n 个节点就是需要删除的节点,并且目前栈顶的节点就是待删除节点的前驱节点。这样一来,删除操作就变得十分方便了。
代码
1、双指针
public ListNode removeNthFromEnd(ListNode head, int n)
{
//头结点为空或要寻找的结点不存在
if (head == null || n < 0)
{
return null;
}
ListNode newHead = new ListNode(0, head);
ListNode p = head; //快指针
ListNode q = newHead; //慢指针
for (int i = 0; i < n; i++) //快指针先走 n 步
{
p = p.next;
}
while (q != null) //寻找倒数第 n 个结点
{
p = p.next;
q = q.next;
}
//q 的下一个节点就是要删除的结点
q.next = q.next.next; //进行删除
return newHead.next;
}
2、计算链表长度
//计算链表长度
public ListNode removeNthFromEnd(ListNode head, int n)
{
int length = getLength(head); //求出链表长度
ListNode newHead = new ListNode(0, head); //创建傀儡结点
ListNode cur = newHead;
//寻找倒数第 n 个结点
for (int i = 0; i < length - n; i++)
{
cur = cur.next;
}
//cur 的下一个节点为要删除的结点
cur.next = cur.next.next;
return newHead.next;
}
private int getLength(ListNode head)
{
int length = 0;
while (head != null)
{
length++;
head = head.next;
}
return length;
}
3、栈
public ListNode removeNthFromEnd(ListNode head, int n)
{
ListNode newHead = new ListNode(0, head);
Deque<ListNode> stack = new LinkedList<>();
ListNode cur = newHead;
//入栈
while (cur != null)
{
stack.push(cur);
cur = cur.next;
}
//出栈:从后往前,第 n 个时结束
for (int i = 0; i < n; i++)
{
stack.pop();
}
ListNode prev = stack.peek();
assert prev != null;
prev.next = prev.next.next;
return newHead.next;
}