我不明白递归关系。
假设我们有这个链表:
head end
↓ ↓
┌───────────┐ ┌───────────┐ ┌───────────┐
│ value: 85 │ │ value: 15 │ │ value: 20 │
│ next: ———————→ │ next: ———————→ ...more nodes...——→ │ next:null │
└───────────┘ └───────────┘ └───────────┘
递归部分基于以下观察:
如果您可以反转一个短一个元素的列表,排除当前head节点,那么我们应该会遇到这样的情况:
head end
↓ ↓
┌───────────┐ ┌───────────┐ ┌───────────┐
│ value: 85 │ │ value: 15 │ │ value: 20 │
│ next: ———————→ │ │ │ │
│ │ │ next:null │ ←——...more nodes...←———————— :next │
└───────────┘ └───────────┘ └───────────┘
↑ ↑
newEnd newHead
在这个阶段,我们不会质疑它是如何做到的。我们只是假设它适用于递归情况。所以如果它工作正常,我们应该得到上面的列表状态。
现在剩余的语句将链接当前的head 节点,以便它完成一个列表的反转工作,包括这个节点也:
newEnd.next = head
这会产生这种状态:
head end
↓ ↓
┌───────────┐ ┌───────────┐ ┌───────────┐
│ value: 85 │ │ value: 15 │ │ value: 20 │
│ next: ———————→ │ │ │ │
│ │ ←——————— :next │ ←——...more nodes...←———————— :next │
└───────────┘ └───────────┘ └───────────┘
↑ ↑
newEnd newHead
然后我们执行:
head.next = None
这两个赋值使当前head成为我们从递归返回的反向列表的尾节点:
head end
↓ ↓
┌───────────┐ ┌───────────┐ ┌───────────┐
│ value: 85 │ │ value: 15 │ │ value: 20 │
│ next:null │ ←——————— :next │ ←——...more nodes...←———————— :next │
└───────────┘ └───────────┘ └───────────┘
↑ ↑
newEnd newHead
现在我们只需要告诉调用者哪个是这个反向列表的头和尾节点:
return newHead, head
当您查看最终状态时,您确实会看到它们是反向列表的头部和尾部。
所以,现在,我们知道了:
- 基本案例有效(您已经很清楚了)
- 递归案例在递归正确返回排除第一个节点的列表的反向列表的条件下起作用
通过归纳,您可以看到,如果它适用于只有一个节点的列表,它也适用于具有 2、3、...等的列表。
备注
- 您链接到的 LeetCode 问题没有使用
end 引用,因此您不需要使用它们。
- 递归有其局限性。当列表很长时,您可能会遇到堆栈溢出异常。
一种迭代方法是在您沿着列表遍历并重新链接每个next 引用时保留对前一个节点的引用。以下是 LeetCode 挑战的工作原理(没有 end 参考):
class Solution:
def reverseList(self, head: ListNode) -> ListNode:
prev = None
while head:
head.next, prev, head = prev, head, head.next
return prev