题目
标题和出处
标题:删除链表的倒数第 N 个结点
难度
3 级
题目描述
要求
给你一个链表,删除链表的倒数第 n \texttt{n} n 个结点,然后返回链表的头结点。
示例
示例 1:
输入:
head
=
[1,2,3,4,5],
n
=
2
\texttt{head = [1,2,3,4,5], n = 2}
head = [1,2,3,4,5], n = 2
输出:
[1,2,3,5]
\texttt{[1,2,3,5]}
[1,2,3,5]
示例 2:
输入:
head
=
[1],
n
=
1
\texttt{head = [1], n = 1}
head = [1], n = 1
输出:
[]
\texttt{[]}
[]
示例 3:
输入:
head
=
[1,2],
n
=
1
\texttt{head = [1,2], n = 1}
head = [1,2], n = 1
输出:
[1]
\texttt{[1]}
[1]
数据范围
- 链表中结点的数目为 sz \texttt{sz} sz
- 1 ≤ sz ≤ 30 \texttt{1} \le \texttt{sz} \le \texttt{30} 1≤sz≤30
- 0 ≤ Node.val ≤ 100 \texttt{0} \le \texttt{Node.val} \le \texttt{100} 0≤Node.val≤100
- 1 ≤ n ≤ sz \texttt{1} \le \texttt{n} \le \texttt{sz} 1≤n≤sz
进阶
你能使用一次遍历实现吗?
解法一
思路和算法
最直观的做法是,首先遍历链表得到链表的结点数量 sz \textit{sz} sz,然后再次遍历链表,找到待删除的结点并将其删除。当链表的结点数量是 sz \textit{sz} sz 时,删除倒数第 n n n 个结点等价于删除正数第 sz − n + 1 \textit{sz} - n + 1 sz−n+1 个结点。
当 n = sz n = \textit{sz} n=sz 时,待删除的结点为链表的头结点,因此返回 head . next \textit{head}.\textit{next} head.next。
当 n < sz n < \textit{sz} n<sz 时,定位到待删除结点的前一个结点 temp \textit{temp} temp,然后将结点 temp . next \textit{temp}.\textit{next} temp.next 删除。具体做法如下:
-
结点 temp \textit{temp} temp 为链表的正数第 sz − n \textit{sz} - n sz−n 个结点,因此从 head \textit{head} head 开始向后移动 sz − n − 1 \textit{sz} - n - 1 sz−n−1 次,即可得到结点 temp \textit{temp} temp;
-
删除 temp \textit{temp} temp 的后一个结点,可通过改变 next \textit{next} next 指针的指向实现,令 temp . next \textit{temp}.\textit{next} temp.next 指向 temp . next . next \textit{temp}.\textit{next}.\textit{next} temp.next.next 即可。
如果待删除的结点是链表的最后一个结点,上述做法同样适用,在删除结点之后, temp . next \textit{temp}.\textit{next} temp.next 将指向 null \text{null} null。
下图为示例 1 的删除结点的过程。此时 sz = 5 \textit{sz} = 5 sz=5, n = 2 n = 2 n=2,待删除的结点是正数第 4 4 4 个结点,因此定位到正数第 3 3 3 个结点,然后令正数第 3 3 3 个结点的 next \textit{next} next 指向正数第 5 5 5 个结点,完成删除操作。
代码
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
int sz = 0;
ListNode temp = head;
while (temp != null) {
sz++;
temp = temp.next;
}
if (n == sz) {
return head.next;
}
temp = head;
int before = sz - n;
for (int i = 1; i < before; i++) {
temp = temp.next;
}
temp.next = temp.next.next;
return head;
}
}
复杂度分析
-
时间复杂度: O ( sz ) O(\textit{sz}) O(sz),其中 sz \textit{sz} sz 是链表的长度。最多需要遍历链表两次,删除结点的时间为 O ( 1 ) O(1) O(1)。
-
空间复杂度: O ( 1 ) O(1) O(1)。
解法二
思路和算法
上述解法需要首先得到链表的结点数量 sz \textit{sz} sz,然后进行删除操作,因此需要两次遍历。其实,链表的结点数量 sz \textit{sz} sz 不需要事先知道,一次遍历也可以完成删除操作。
由于待删除的是倒数第 n n n 个结点,因此可以想到使用两个指针,这两个指针指向的结点在链表中相差 n n n 个位置。用 temp 1 \textit{temp}_1 temp1 和 temp 2 \textit{temp}_2 temp2 分别表示两个指针,其中 temp 2 \textit{temp}_2 temp2 在 temp 1 \textit{temp}_1 temp1 的后面 n n n 个位置。当 temp 2 \textit{temp}_2 temp2 指向链表的最后一个结点时, temp 1 \textit{temp}_1 temp1 指向待删除结点的前一个结点。
由于待删除的结点可能是链表的头结点,因此需要创建哑节点 dummyHead \textit{dummyHead} dummyHead,使得 dummyHead . next = head \textit{dummyHead}.\textit{next} = \textit{head} dummyHead.next=head。将两个指针 temp 1 \textit{temp}_1 temp1 和 temp 2 \textit{temp}_2 temp2 初始化为都指向 dummyHead \textit{dummyHead} dummyHead,然后将 temp 2 \textit{temp}_2 temp2 向后移动 n n n 次,即满足 temp 2 \textit{temp}_2 temp2 在 temp 1 \textit{temp}_1 temp1 的后面 n n n 个位置。
当 temp 1 \textit{temp}_1 temp1 和 temp 2 \textit{temp}_2 temp2 满足相差 n n n 个位置时,同时将两个指针向后移动,直到 temp 2 \textit{temp}_2 temp2 指向链表的最后一个结点,此时 temp 1 \textit{temp}_1 temp1 指向待删除结点的前一个结点。将 temp 1 \textit{temp}_1 temp1 定位到待删除结点的前一个结点之后,令 temp 1 . next \textit{temp}_1.\textit{next} temp1.next 指向 temp 1 . next . next \textit{temp}_1.\textit{next}.\textit{next} temp1.next.next,即可完成删除操作。
完成删除操作之后,新的头结点为哑节点的下一个结点,因此返回 dummyHead . next \textit{dummyHead}.\textit{next} dummyHead.next。
下图为示例 1 的删除结点的过程,图中的灰色结点表示哑节点。此时 n = 2 n = 2 n=2,因此将 temp 2 \textit{temp}_2 temp2 移动到和 temp 1 \textit{temp}_1 temp1 相差 2 2 2 个位置,然后同时向后移动 temp 1 \textit{temp}_1 temp1 和 temp 2 \textit{temp}_2 temp2,直到 temp 2 \textit{temp}_2 temp2 指向链表的最后一个结点, temp 1 \textit{temp}_1 temp1 指向待删除结点的前一个结点,删除 temp 1 \textit{temp}_1 temp1 指向的结点的下一个结点。最后返回哑节点的下一个结点。
代码
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummyHead = new ListNode(0, head);
ListNode temp1 = dummyHead, temp2 = dummyHead;
for (int i = 0; i < n; i++) {
temp2 = temp2.next;
}
while (temp2.next != null) {
temp1 = temp1.next;
temp2 = temp2.next;
}
temp1.next = temp1.next.next;
return dummyHead.next;
}
}
复杂度分析
-
时间复杂度: O ( sz ) O(\textit{sz}) O(sz),其中 sz \textit{sz} sz 是链表的长度。需要遍历链表一次,删除结点的时间为 O ( 1 ) O(1) O(1)。
-
空间复杂度: O ( 1 ) O(1) O(1)。