给你一个链表,删除链表的倒数第 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 <= sz <= 30
0 <= Node.val <= 100
1 <= n <= sz
进阶: 你能尝试使用一趟扫描实现吗?
📺 视频题解
📖 文字题解 前言 在对链表进行操作时,一种常用的技巧是添加一个哑节点(dummy node),它的 $\textit{next}$ 指针指向链表的头节点。这样一来,我们就不需要对头节点进行特殊的判断了。
例如,在本题中,如果我们要删除节点 $y$,我们需要知道节点 $y$ 的前驱节点 $x$,并将 $x$ 的指针指向 $y$ 的后继节点。但由于头节点不存在前驱节点,因此我们需要在删除头节点时进行特殊判断。但如果我们添加了哑节点,那么头节点的前驱节点就是哑节点本身,此时我们就只需要考虑通用的情况即可。
特别地,在某些语言中,由于需要自行对内存进行管理。因此在实际的面试中,对于「是否需要释放被删除节点对应的空间」这一问题,我们需要和面试官进行积极的沟通以达成一致。下面的代码中默认不释放空间。
方法一:计算链表长度 思路与算法
一种容易想到的方法是,我们首先从头节点开始对链表进行一次遍历,得到链表的长度 $L$。随后我们再从头节点开始对链表进行一次遍历,当遍历到第 $L-n+1$ 个节点时,它就是我们需要删除的节点。
为了与题目中的 $n$ 保持一致,节点的编号从 $1$ 开始,头节点为编号 $1$ 的节点。
为了方便删除操作,我们可以从哑节点开始遍历 $L-n+1$ 个节点。当遍历到第 $L-n+1$ 个节点时,它的下一个节点 就是我们需要删除的节点,这样我们只需要修改一次指针,就能完成删除操作。
代码
[sol1-C++] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class Solution {public : int getLength (ListNode* head) { int length = 0 ; while (head) { ++length; head = head->next; } return length; } ListNode* removeNthFromEnd (ListNode* head, int n) { ListNode* dummy = new ListNode (0 , head); int length = getLength (head); ListNode* cur = dummy; for (int i = 1 ; i < length - n + 1 ; ++i) { cur = cur->next; } cur->next = cur->next->next; ListNode* ans = dummy->next; delete dummy; return ans; } };
[sol1-Java] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution { public ListNode removeNthFromEnd (ListNode head, int n) { ListNode dummy = new ListNode (0 , head); int length = getLength(head); ListNode cur = dummy; for (int i = 1 ; i < length - n + 1 ; ++i) { cur = cur.next; } cur.next = cur.next.next; ListNode ans = dummy.next; return ans; } public int getLength (ListNode head) { int length = 0 ; while (head != null ) { ++length; head = head.next; } return length; } }
[sol1-Python3] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Solution : def removeNthFromEnd (self, head: ListNode, n: int ) -> ListNode: def getLength (head: ListNode ) -> int : length = 0 while head: length += 1 head = head.next return length dummy = ListNode(0 , head) length = getLength(head) cur = dummy for i in range (1 , length - n + 1 ): cur = cur.next cur.next = cur.next .next return dummy.next
[sol1-Golang] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 func getLength (head *ListNode) (length int ) { for ; head != nil ; head = head.Next { length++ } return } func removeNthFromEnd (head *ListNode, n int ) *ListNode { length := getLength(head) dummy := &ListNode{0 , head} cur := dummy for i := 0 ; i < length-n; i++ { cur = cur.Next } cur.Next = cur.Next.Next return dummy.Next }
[sol1-C] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 int getLength (struct ListNode* head) { int length = 0 ; while (head) { ++length; head = head->next; } return length; } struct ListNode* removeNthFromEnd (struct ListNode* head, int n) { struct ListNode * dummy = malloc (sizeof (struct ListNode)); dummy->val = 0 , dummy->next = head; int length = getLength(head); struct ListNode * cur = dummy; for (int i = 1 ; i < length - n + 1 ; ++i) { cur = cur->next; } cur->next = cur->next->next; struct ListNode * ans = dummy->next; free (dummy); return ans; }
复杂度分析
方法二:栈 思路与算法
我们也可以在遍历链表的同时将所有节点依次入栈。根据栈「先进后出」的原则,我们弹出栈的第 $n$ 个节点就是需要删除的节点,并且目前栈顶的节点就是待删除节点的前驱节点。这样一来,删除操作就变得十分方便了。
< , , , , , , , , , >
代码
[sol2-C++] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution {public : ListNode* removeNthFromEnd (ListNode* head, int n) { ListNode* dummy = new ListNode (0 , head); stack<ListNode*> stk; ListNode* cur = dummy; while (cur) { stk.push (cur); cur = cur->next; } for (int i = 0 ; i < n; ++i) { stk.pop (); } ListNode* prev = stk.top (); prev->next = prev->next->next; ListNode* ans = dummy->next; delete dummy; return ans; } };
[sol2-Java] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution { public ListNode removeNthFromEnd (ListNode head, int n) { ListNode dummy = new ListNode (0 , head); Deque<ListNode> stack = new LinkedList <ListNode>(); ListNode cur = dummy; while (cur != null ) { stack.push(cur); cur = cur.next; } for (int i = 0 ; i < n; ++i) { stack.pop(); } ListNode prev = stack.peek(); prev.next = prev.next.next; ListNode ans = dummy.next; return ans; } }
[sol2-Python3] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Solution : def removeNthFromEnd (self, head: ListNode, n: int ) -> ListNode: dummy = ListNode(0 , head) stack = list () cur = dummy while cur: stack.append(cur) cur = cur.next for i in range (n): stack.pop() prev = stack[-1 ] prev.next = prev.next .next return dummy.next
[sol2-Golang] 1 2 3 4 5 6 7 8 9 10 func removeNthFromEnd (head *ListNode, n int ) *ListNode { nodes := []*ListNode{} dummy := &ListNode{0 , head} for node := dummy; node != nil ; node = node.Next { nodes = append (nodes, node) } prev := nodes[len (nodes)-1 -n] prev.Next = prev.Next.Next return dummy.Next }
[sol2-C] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 struct Stack { struct ListNode * val ; struct Stack * next ; }; struct ListNode* removeNthFromEnd (struct ListNode* head, int n) { struct ListNode * dummy = malloc (sizeof (struct ListNode)); dummy->val = 0 , dummy->next = head; struct Stack * stk = NULL ; struct ListNode * cur = dummy; while (cur) { struct Stack * tmp = malloc (sizeof (struct Stack)); tmp->val = cur, tmp->next = stk; stk = tmp; cur = cur->next; } for (int i = 0 ; i < n; ++i) { struct Stack * tmp = stk->next; free (stk); stk = tmp; } struct ListNode * prev = stk->val; prev->next = prev->next->next; struct ListNode * ans = dummy->next; free (dummy); return ans; }
复杂度分析
方法三:双指针 思路与算法
我们也可以在不预处理出链表的长度,以及使用常数空间的前提下解决本题。
由于我们需要找到倒数第 $n$ 个节点,因此我们可以使用两个指针 $\textit{first}$ 和 $\textit{second}$ 同时对链表进行遍历,并且 $\textit{first}$ 比 $\textit{second}$ 超前 $n$ 个节点。当 $\textit{first}$ 遍历到链表的末尾时,$\textit{second}$ 就恰好处于倒数第 $n$ 个节点。
具体地,初始时 $\textit{first}$ 和 $\textit{second}$ 均指向头节点。我们首先使用 $\textit{first}$ 对链表进行遍历,遍历的次数为 $n$。此时,$\textit{first}$ 和 $\textit{second}$ 之间间隔了 $n-1$ 个节点,即 $\textit{first}$ 比 $\textit{second}$ 超前了 $n$ 个节点。
在这之后,我们同时使用 $\textit{first}$ 和 $\textit{second}$ 对链表进行遍历。当 $\textit{first}$ 遍历到链表的末尾(即 $\textit{first}$ 为空指针)时,$\textit{second}$ 恰好指向倒数第 $n$ 个节点。
根据方法一和方法二,如果我们能够得到的是倒数第 $n$ 个节点的前驱节点而不是倒数第 $n$ 个节点的话,删除操作会更加方便。因此我们可以考虑在初始时将 $\textit{second}$ 指向哑节点,其余的操作步骤不变。这样一来,当 $\textit{first}$ 遍历到链表的末尾时,$\textit{second}$ 的下一个节点 就是我们需要删除的节点。
代码
[sol3-C++] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution {public : ListNode* removeNthFromEnd (ListNode* head, int n) { ListNode* dummy = new ListNode (0 , head); ListNode* first = head; ListNode* second = dummy; for (int i = 0 ; i < n; ++i) { first = first->next; } while (first) { first = first->next; second = second->next; } second->next = second->next->next; ListNode* ans = dummy->next; delete dummy; return ans; } };
[sol3-Java] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution { public ListNode removeNthFromEnd (ListNode head, int n) { 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 ans = dummy.next; return ans; } }
[sol3-Python3] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Solution : def removeNthFromEnd (self, head: ListNode, n: int ) -> ListNode: dummy = ListNode(0 , head) first = head second = dummy for i in range (n): first = first.next while first: first = first.next second = second.next second.next = second.next .next return dummy.next
[sol3-Golang] 1 2 3 4 5 6 7 8 9 10 11 12 func removeNthFromEnd (head *ListNode, n int ) *ListNode { dummy := &ListNode{0 , head} first, second := head, dummy for i := 0 ; i < n; i++ { first = first.Next } for ; first != nil ; first = first.Next { second = second.Next } second.Next = second.Next.Next return dummy.Next }
[sol3-C] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 struct ListNode* removeNthFromEnd (struct ListNode* head, int n) { struct ListNode * dummy = malloc (sizeof (struct ListNode)); dummy->val = 0 , dummy->next = head; struct ListNode * first = head; struct ListNode * second = dummy; for (int i = 0 ; i < n; ++i) { first = first->next; } while (first) { first = first->next; second = second->next; } second->next = second->next->next; struct ListNode * ans = dummy->next; free (dummy); return ans; }
复杂度分析