questions M-R

This commit is contained in:
2025-05-25 12:43:25 +01:00
parent 917c371529
commit 68699f35ec
62 changed files with 12841 additions and 0 deletions

View File

@@ -0,0 +1,174 @@
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
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
&nbsp;
**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`
&nbsp;
**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
&nbsp;
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.