题目
来源:LeetCode.
给你一个链表,删除链表的倒数第 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]
提示:
- 链表中结点的数目为 sz
- 1 < = s z < = 30 1 <= sz <= 30 1<=sz<=30
- 0 < = N o d e . v a l < = 100 0 <= Node.val <= 100 0<=Node.val<=100
- 1 < = n < = s z 1 <= n <= sz 1<=n<=sz
接下来看一下解题思路:
思路一:计算链表长度:
首先从头节点开始对链表进行一次遍历,得到链表的长度
L
L
L;
随后我们再从头节点开始对链表进行一次遍历,当遍历到第
L
−
n
+
1
L-n+1
L−n+1 个节点时,它就是我们需要删除的节点;
为了方便删除操作,我们可以从哑节点开始遍历
L
−
n
+
1
L-n+1
L−n+1 个节点;
当遍历到第
L
−
n
+
1
L-n+1
L−n+1 个节点时,它的下一个节点就是我们需要删除的节点,这样我们只需要修改一次指针,就能完成删除操作;
代码
public static ListNode removeNthFromEnd1(ListNode head, int n) {
if (head == null) {
return null;
}
int count = getListLength(head);
ListNode dummy = new ListNode(0, head);
ListNode cur = dummy;
for (int i = 1; i < count - n + 1; ++i) {
cur = cur.next;
}
cur.next = cur.next.next;
ListNode node = dummy.next;
return node;
}
private static int getListLength(ListNode head) {
int count = 0;
for (ListNode p = head; p != null ; p = p.next) {
++count;
}
return count;
}
总结
时间复杂度:
O
(
L
)
O(L)
O(L),其中
L
L
L 是链表的长度
空间复杂度:
O
(
1
)
O(1)
O(1)
思路二:栈:
可以在遍历链表的同时将所有节点依次入栈;
根据栈「先进后出」的原则,我们弹出栈的第
n
n
n 个节点就是需要删除的节点,并且目前栈顶的节点就是待删除节点的前驱节点;
代码
public static ListNode removeNthFromEnd2(ListNode head, int n) {
if (head == null) {
return null;
}
ListNode dummy = new ListNode(0, head);
Stack<ListNode> stack = new Stack<>();
for (ListNode p = dummy; p != null ; p = p.next) {
stack.push(p);
}
for (int i = 0; i < n; ++i) {
stack.pop();
}
ListNode cur = stack.peek();
cur.next = cur.next.next;
ListNode node = dummy.next;
return node;
}
总结
时间复杂度:
O
(
L
)
O(L)
O(L),其中
L
L
L 是链表的长度。
空间复杂度:
O
(
L
)
O(L)
O(L),其中
L
L
L 是链表的长度。主要为栈的开销。
思路三:双指针:
可以使用两个指针
first
\textit{first}
first 和
second
\textit{second}
second 同时对链表进行遍历;
first
\textit{first}
first 比
second
\textit{second}
second 超前
n
n
n 个节点。当
first
\textit{first}
first 遍历到链表的末尾时,
second
\textit{second}
second 就恰好处于倒数第
n
n
n 个节点;
初始时
first
\textit{first}
first 和
second
\textit{second}
second 均指向头节点。我们首先使用
first
\textit{first}
first对链表进行遍历,遍历的次数为
n
n
n。此时,
first
\textit{first}
first 和
second
\textit{second}
second 之间间隔了
n
−
1
n−1
n−1 个节点,即
first
\textit{first}
first 比
second
\textit{second}
second 超前了
n
n
n 个节点;
在这之后,我们同时使用
first
\textit{first}
first 和
second
\textit{second}
second 对链表进行遍历。当
first
\textit{first}
first 遍历到链表的末尾(即
first
\textit{first}
first 为空指针)时,
second
\textit{second}
second 恰好指向倒数第
n
n
n 个节点;
为了删除方便,可以初始时将
second
\textit{second}
second 指向哑节点,当
first
\textit{first}
first 遍历到链表的末尾时,
second
\textit{second}
second 的下一个节点就是我们需要删除的节点;
代码
public static ListNode removeNthFromEnd(ListNode head, int n) {
if (head == null) {
return null;
}
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 node = dummy.next;
return node;
}
总结
时间复杂度:
O
(
L
)
O(L)
O(L),其中
L
L
L 是链表的长度。
空间复杂度:
O
(
1
)
O(1)
O(1)。