给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。 python3
时间: 2025-04-13 19:52:40 浏览: 15
<think>嗯,我现在需要解决一个关于链表的问题:给定一个链表,删除它的倒数第第n个节点,并返回头节点。用Python3来实现。这个问题看起来应该是中等难度的,我记得链表操作常需要双指针或者快慢指针的技巧。那让我仔细想想怎么处理。
首先,我得理解问题。链表的结构通常是一个节点包含值和指向下一个节点的指针。删除倒数第n个节点,意味着需要找到这个节点,然后调整指针跳过它。比如,如果链表是1->2->3->4->5,删除倒数第2个的话,结果应该是1->2->3->5。这时候,原来的第四个节点(值为4)被删除了。
那怎么找到倒数第n个节点呢?通常的做法是使用两个指针,快指针先走n步,然后快慢指针一起移动,直到快指针到达末尾。这样慢指针就会指向需要删除的节点的前一个节点。但是这里有个问题,如果要删除的是头节点怎么办?比如链表长度刚好是n,那么倒数第n个节点就是头节点,这时候需要返回头节点的下一个节点。
这时候可能需要使用一个哑节点(dummy node)来处理这种情况,这样可以统一处理所有情况,包括删除头节点的情况。哑节点的下一个节点是头节点,这样在寻找前驱节点的时候,即使要删除的是头节点,也能正确找到前驱节点(即哑节点)。
步骤大概是这样的:
1. 创建哑节点dummy,并将其next指向head。
2. 初始化快慢指针都指向dummy。
3. 快指针先移动n+1步,这样当快指针到达末尾时,慢指针会指向要删除节点的前一个。
(比如,当n=2时,快指针先走3步,然后快慢一起走,直到快指针到None,此时慢指针的next是要删除的节点)
4. 移动快指针直到其到达链表末尾。
5. 此时慢指针的next节点就是需要删除的节点,将慢指针的next指向next.next,完成删除。
6. 返回dummy.next作为新的头节点。
那在实现的时候,需要注意边界情况,比如链表长度为1,n=1的情况,这时候删除后应该返回None。或者当n等于链表长度的时候,删除头节点。
举个例子,假设链表是1->2->3->4->5,n=2。那么需要删除4。快指针先走n+1步?或者是不是应该先让快指针走n步,然后快慢一起移动,直到快指针到末尾?可能我之前的步骤描述有点问题,需要再仔细分析。
正确的步骤应该是:假设链表长度是L,那么倒数第n个节点的位置是L - n + 1。为了找到这个节点的前驱节点,我们需要走到L - n的位置。使用双指针,快指针先走n步,然后快慢指针一起走剩下的(L -n)步,这样慢指针就会指向L -n的位置,即前驱节点。
或者可能我的初始思路有误。正确的做法应该是:
- 快指针先走n步,这样当快指针到达末尾的时候,慢指针正好在倒数第n个节点的前驱。
例如,链表长度为5,n=2。快指针先走2步,到达第三个节点。然后快慢指针一起移动,直到快指针到达最后一个节点(第五个节点)。此时快指针移动了5-2=3步,慢指针移动了3步,从哑节点出发,移动到第三个节点,也就是节点3,此时慢指针的下一个节点是节点4,即要删除的节点。这样处理是正确的。
所以步骤应该是:
1. 创建dummy节点,指向head。
2. 初始化slow和fast都为dummy。
3. 将fast向前移动n步。如果在移动过程中fast变为None,说明n大于链表长度,可能需要处理这种情况。不过题目可能假设n是有效的。
4. 然后同时移动slow和fast,直到fast.next为None。此时slow指向要删除节点的前驱。
5. 将slow.next指向slow.next.next。
6. 返回dummy.next。
那在Python中,如何实现这个呢?
首先定义链表的节点类,通常题目可能已经给出,比如:
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
然后,函数的输入是head和n,返回头节点。
然后处理步骤:
def removeNthFromEnd(head: ListNode, n: int) -> ListNode:
dummy = ListNode(0)
dummy.next = head
slow = fast = dummy
# 先移动fast指针n步
for _ in range(n):
fast = fast.next
# 然后同时移动,直到fast到达最后一个节点
while fast.next:
slow = slow.next
fast = fast.next
# 此时slow.next是要删除的节点
slow.next = slow.next.next
return dummy.next
这样是否正确?
比如测试案例,链表1->2->3->4->5,n=2。dummy ->1->2->3->4->5.
初始fast和slow都指向dummy.
移动fast两次,到达节点2(fast从dummy移动两步,先到dummy.next(1),然后到2)。然后,当fast.next存在时,循环继续。此时fast是节点2,fast.next是3。进入循环:
slow移动到1,fast到3. fast.next是4,继续循环:slow到2,fast到4. fast.next是5,继续循环:slow到3,fast到5. 此时fast.next是None,循环停止。此时slow是3,slow.next是4。删除后,3的next是5。链表变为1->2->3->5。正确。
另一个测试案例,当n等于链表长度,比如链表长度为5,n=5。这时候要删除头节点。例如链表是1->2->3->4->5,n=5。
初始化后,dummy.next是1。fast移动5次。但初始时fast是dummy。移动5次的话:
第一次:fast到1;第二次到2;第三次到3;第四次到4;第五次到5。此时,fast是5。此时,检查fast.next是否为None。此时,fast.next是None。循环不执行。slow仍然是dummy。这时候,slow.next是1。删除之后,dummy.next变成2。所以返回dummy.next是2。正确。
另一个情况,链表只有一个节点,n=1。那么返回None。
那代码似乎能处理这些情况。但是,是否需要考虑当n大于链表长度的情况?题目可能保证n是有效的,所以可能不需要处理。比如题目中可能给出n的范围是1到链表长度。
综上,代码应该可以正确工作。</think>### 逐步解答
要删除链表的倒数第 $n$ 个节点,可以使用**双指针(快慢指针)**技巧,结合**哑节点(dummy node)**来简化边界条件处理。具体步骤如下:
1. **创建哑节点**:
哑节点的 `next` 指向链表头,用于统一处理删除头节点的情况。
2. **初始化快慢指针**:
快指针 `fast` 和慢指针 `slow` 都初始化为哑节点。
3. **快指针先移动 $n$ 步**:
让 `fast` 提前移动 $n$ 步,此时 `fast` 与 `slow` 间隔 $n$ 个节点。
4. **同步移动快慢指针**:
同时移动 `fast` 和 `slow`,直到 `fast` 到达链表末尾(`fast.next` 为 `None`)。此时 `slow` 指向倒数第 $n+1$ 个节点(即要删除节点的前驱)。
5. **删除目标节点**:
将 `slow.next` 指向 `slow.next.next`,跳过倒数第 $n$ 个节点。
6. **返回新头节点**:
返回哑节点的 `next`,即新链表的头。
---
### Python3 代码实现
```python
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def removeNthFromEnd(head: ListNode, n: int) -> ListNode:
dummy = ListNode(0) # 创建哑节点
dummy.next = head
slow = fast = dummy # 初始化快慢指针
# 快指针先移动 n 步
for _ in range(n):
fast = fast.next
# 同步移动,直到快指针到末尾
while fast.next:
slow = slow.next
fast = fast.next
# 删除倒数第 n 个节点
slow.next = slow.next.next
return dummy.next # 返回新头节点
```
---
### 示例分析
假设链表为 `1 -> 2 -> 3 -> 4 -> 5`,删除倒数第 $2$ 个节点:
1. **初始化哑节点**:
`dummy -> 1 -> 2 -> 3 -> 4 -> 5`,`slow` 和 `fast` 均指向 `dummy`。
2. **快指针移动 $2$ 步**:
`fast` 移动到节点 `2`。
3. **同步移动快慢指针**:
- `fast` 移动到 `3`,`slow` 移动到 `1`
- `fast` 移动到 `4`,`slow` 移动到 `2`
- `fast` 移动到 `5`,`slow` 移动到 `3`
4. **删除节点**:
`slow.next`(节点 `4`)被跳过,链表变为 `1 -> 2 -> 3 -> 5`。
5. **返回新头节点**:
最终返回 `dummy.next`(即节点 `1`)。
---
### 复杂度分析
- **时间复杂度**:$O(L)$,$L$ 为链表长度,需遍历整个链表。
- **空间复杂度**:$O(1)$,仅使用常数级额外空间。
阅读全文
相关推荐


















