201 lines
7.6 KiB
YAML
201 lines
7.6 KiB
YAML
title: Reverse Linked List
|
|
slug: reverse-linked-list
|
|
difficulty: easy
|
|
leetcode_id: 206
|
|
leetcode_url: https://leetcode.com/problems/reverse-linked-list/
|
|
categories:
|
|
- linked-lists
|
|
- recursion
|
|
patterns:
|
|
- linkedlist-reversal
|
|
|
|
function_signature: "def reverse_list(head: ListNode | None) -> ListNode | None:"
|
|
|
|
test_cases:
|
|
visible:
|
|
- input: { head: [1, 2, 3, 4, 5] }
|
|
expected: [5, 4, 3, 2, 1]
|
|
- input: { head: [1, 2] }
|
|
expected: [2, 1]
|
|
- input: { head: [] }
|
|
expected: []
|
|
hidden:
|
|
- input: { head: [1] }
|
|
expected: [1]
|
|
- input: { head: [1, 2, 3] }
|
|
expected: [3, 2, 1]
|
|
- input: { head: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] }
|
|
expected: [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
|
|
- input: { head: [-5, 0, 5] }
|
|
expected: [5, 0, -5]
|
|
- input: { head: [1, 1, 1, 1] }
|
|
expected: [1, 1, 1, 1]
|
|
- input: { head: [100, -100] }
|
|
expected: [-100, 100]
|
|
- input: { head: [7] }
|
|
expected: [7]
|
|
|
|
description: |
|
|
Given the `head` of a singly linked list, reverse the list, and return *the reversed list*.
|
|
|
|
constraints: |
|
|
- The number of nodes in the list is in the range `[0, 5000]`
|
|
- `-5000 <= Node.val <= 5000`
|
|
|
|
examples:
|
|
- input: "head = [1,2,3,4,5]"
|
|
output: "[5,4,3,2,1]"
|
|
explanation: "The list 1→2→3→4→5 becomes 5→4→3→2→1."
|
|
- input: "head = [1,2]"
|
|
output: "[2,1]"
|
|
explanation: "The list 1→2 becomes 2→1."
|
|
- input: "head = []"
|
|
output: "[]"
|
|
explanation: "An empty list remains empty when reversed."
|
|
|
|
explanation:
|
|
intuition: |
|
|
Imagine a chain of paper clips linked together, each pointing to the next. To reverse the chain, you need to make each clip point to the *previous* one instead of the next one.
|
|
|
|
Think of it like this: for each node, we're changing the direction of its arrow. Originally `A → B → C`, we want `A ← B ← C`. The challenge is that when we change a node's `next` pointer to point backwards, we lose our reference to move forward!
|
|
|
|
The solution is to use **three pointers**:
|
|
- `prev`: The node we're pointing back to
|
|
- `curr`: The node we're currently processing
|
|
- `next_node`: Saved reference to the next node (so we don't lose it when we reverse the link)
|
|
|
|
After processing all nodes, the original tail becomes the new head. Since `curr` ends up as `None` (past the last node), `prev` points to the last processed node — our new head.
|
|
|
|
approach: |
|
|
We solve this using an **Iterative Three-Pointer Approach**:
|
|
|
|
**Step 1: Initialise pointers**
|
|
|
|
- `prev = None`: The new tail will point to `None`
|
|
- `curr = head`: Start at the beginning of the list
|
|
- We don't initialise `next_node` yet — we'll set it inside the loop
|
|
|
|
|
|
|
|
**Step 2: Iterate through the list**
|
|
|
|
- While `curr` is not `None`:
|
|
- **Save the next node**: `next_node = curr.next` (before we lose it!)
|
|
- **Reverse the link**: `curr.next = prev` (point backwards)
|
|
- **Move prev forward**: `prev = curr`
|
|
- **Move curr forward**: `curr = next_node`
|
|
|
|
|
|
|
|
**Step 3: Return the new head**
|
|
|
|
- When the loop ends, `curr` is `None` (we've passed the last node)
|
|
- `prev` points to what was the last node — now the first node
|
|
- Return `prev` as the new head
|
|
|
|
|
|
|
|
This processes each node exactly once with constant extra space.
|
|
|
|
common_pitfalls:
|
|
- title: Losing the Reference to the Next Node
|
|
description: |
|
|
If you reverse the link *before* saving the next node, you can't continue traversing!
|
|
|
|
```python
|
|
# WRONG: We lose access to the rest of the list
|
|
curr.next = prev
|
|
curr = curr.next # This now points backwards!
|
|
```
|
|
|
|
Always save `next_node = curr.next` **before** modifying `curr.next`.
|
|
wrong_approach: "curr.next = prev; curr = curr.next"
|
|
correct_approach: "next_node = curr.next; curr.next = prev; curr = next_node"
|
|
|
|
- title: Returning curr Instead of prev
|
|
description: |
|
|
After the loop, `curr` is `None` — we've moved past the end of the list. The actual new head is `prev`, which points to the last node we processed.
|
|
|
|
Think about it: in the final iteration, `curr` points to the last node, we process it, then `curr = next_node` makes `curr = None`.
|
|
wrong_approach: "return curr"
|
|
correct_approach: "return prev"
|
|
|
|
- title: Not Handling Empty or Single-Node Lists
|
|
description: |
|
|
The algorithm handles these edge cases naturally:
|
|
- **Empty list**: `head = None`, loop never executes, return `prev = None`
|
|
- **Single node**: Loop runs once, `prev` becomes the single node, which is returned
|
|
|
|
No special-case code is needed, but it's good to trace through these cases to verify.
|
|
wrong_approach: "Adding unnecessary if-checks for edge cases"
|
|
correct_approach: "Trust the algorithm — it handles empty and single-node lists"
|
|
|
|
key_takeaways:
|
|
- "**Fundamental operation**: Reversing a linked list is used in many problems (palindrome check, reverse in groups, etc.)"
|
|
- "**Three-pointer technique**: `prev`, `curr`, `next` is a common pattern for in-place linked list manipulation"
|
|
- "**Save before modifying**: When changing pointers, always save references you'll need later"
|
|
- "**Both iterative and recursive work**: Iterative is O(1) space, recursive is O(n) but more elegant — know both!"
|
|
|
|
time_complexity: "O(n). We visit each of the n nodes exactly once, performing O(1) work at each."
|
|
space_complexity: "O(1). We only use three pointer variables, regardless of list length."
|
|
|
|
solutions:
|
|
- approach_name: Iterative
|
|
is_optimal: true
|
|
code: |
|
|
class ListNode:
|
|
def __init__(self, val=0, next=None):
|
|
self.val = val
|
|
self.next = next
|
|
|
|
def reverse_list(head: ListNode | None) -> ListNode | None:
|
|
prev = None # Will become the new tail
|
|
curr = head # Start at the head
|
|
|
|
while curr:
|
|
# Save next node before we overwrite the link
|
|
next_node = curr.next
|
|
|
|
# Reverse the link: point backwards
|
|
curr.next = prev
|
|
|
|
# Move pointers forward
|
|
prev = curr
|
|
curr = next_node
|
|
|
|
# prev is now the new head (curr is None)
|
|
return prev
|
|
explanation: |
|
|
**Time Complexity:** O(n) — Single pass through all nodes.
|
|
|
|
**Space Complexity:** O(1) — Only three pointers used.
|
|
|
|
We iterate through the list once, reversing each link as we go. The key is saving the next node before modifying `curr.next`, then advancing both `prev` and `curr` forward.
|
|
|
|
- approach_name: Recursive
|
|
is_optimal: false
|
|
code: |
|
|
def reverse_list(head: ListNode | None) -> ListNode | None:
|
|
# Base case: empty list or single node
|
|
if not head or not head.next:
|
|
return head
|
|
|
|
# Recursively reverse the rest of the list
|
|
new_head = reverse_list(head.next)
|
|
|
|
# head.next is now the tail of the reversed sublist
|
|
# Make it point back to head
|
|
head.next.next = head
|
|
|
|
# head is now the tail, so point it to None
|
|
head.next = None
|
|
|
|
# Return the new head (unchanged through recursion)
|
|
return new_head
|
|
explanation: |
|
|
**Time Complexity:** O(n) — Each node processed once.
|
|
|
|
**Space Complexity:** O(n) — Recursion stack depth equals list length.
|
|
|
|
The recursive approach reverses the rest of the list first, then fixes the link for the current node. `head.next.next = head` makes the next node point back to us, and `head.next = None` makes us the new tail. The new head is propagated back through all recursive calls.
|