删除链表倒数第n个节点
链表定义
// @lc code=start
/**
* 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; }
* }
*/
方法一:倒推节点顺序位置
解题方法:
添加哑节点统一处理逻辑(不用额外判定头节点),只有一个节点时头节点的前驱节点就是哑结点
计算链表一共有多少个节点m
要删除节点的前驱节点就是链表中第m-n个节点
时间复杂度:O(n)
空间复杂度:O(1)
public ListNode removeNthFromEnd(ListNode head, int n) {
/**
* 添加哑节点统一处理逻辑(不用额外判定头节点),只有一个节点时头节点的前驱节点就是哑结点
* -1 -> 1 -> null
* 计算链表一共有多少个节点m
* 要删除节点的前驱节点就是链表中第m-n个节点
*/
if(head==null){
return null;
}
ListNode dummy = new ListNode(-1);
dummy.next=head;
int m=1;
ListNode p=dummy.next;
while (p!=null){
p=p.next;
m++;
}
ListNode prev=dummy;
for (int i = 1; i <m-n; i++) {
prev=prev.next;
}
prev.next= prev.next.next;
return dummy.next;
}
方法二:栈
思路:利用栈的先进后出特性
解题方法:
将链表所有节点入栈
出栈的第n个节点就是要删除的节点,而栈顶节点就是前驱节点
有了哑节点后,就不用对头结点做特殊判断了
时间复杂度:O(n)
空间复杂度:O(n)
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
/**
* 将节点入栈
* 出栈的第n个节点就是要删除的节点,而栈顶节点就是前置节点
*/
ListNode pre;
ListNode dummy=new ListNode(-1);
dummy.next=head;
Deque<ListNode> stack = new LinkedList<>();
ListNode p=dummy;
while(p!=null){
stack.push(p);
p=p.next;
}
for (int i=0;i<n;i++){
stack.pop();
}
pre=stack.peek();
pre.next=pre.next.next;
return dummy.next;
}
}
方法三:双指针
思路:利用快慢指针的相差距离定位前驱节点
解题方法:
添加哑节点统一处理逻辑(不用额外判定头节点),只有一个节点时头节点的前驱节点就是哑结点
定义快慢两个指针,初始化时快指针和慢指针相差n个节点
快指针和慢指针分别向前移动
当快指针走到链尾时,慢指针就是要删除节点的前驱节点
时间复杂度:O(n)
空间复杂度:O(1)
public ListNode removeNthFromEnd(ListNode head, int n) {
/**
* 添加哑节点统一处理逻辑(不用额外判定头节点),只有一个节点时头节点的前驱节点就是哑结点
* 双指针
* 定义快慢两个指针,初始化时快指针和慢指针相差n个节点
* 快指针和慢指针分别向前移动
* 当快指针走到链尾时,慢指针就是要删除节点的前驱节点
* s f
*-1 -> 1 -> 2 -> 3 -> 4 -> 5
* s f
* s f
*/
if (head==null){
return null;
}
ListNode dummy=new ListNode(-1);
dummy.next=head;
ListNode slow=dummy;
ListNode fast=dummy;
for (int i = 0; i < n; i++) {
fast= fast.next;
}
while (fast.next!=null){
slow=slow.next;
fast=fast.next;
}
slow.next=slow.next.next;
return dummy.next;
}