Skip to content

Commit ab8c0ea

Browse files
committed
Complete Chapter 4 (Linked Lists); refine tabs for plain-text blocks
Chapter 4 (Linked Lists): primer + 5 flagship pages (Reverse, Cycle, Merge Two Sorted, Remove Nth From End, Cycle II with entry math). Tabs refinement: only runs containing a NAMED language become tab groups; adjacent unlabeled ASCII diagrams / dry runs are wrapped individually (copy button only) instead of becoming 'text|text' tabs. Verified via jsdom: reverse page now has exactly one 5-language tab group; ch3 pages still behave (Kotlin default, switching, All).
1 parent e48b754 commit ab8c0ea

9 files changed

Lines changed: 1098 additions & 2 deletions

File tree

‎CodingInterviewFightClub/src/SUMMARY.md‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,11 @@
5757
- [3.6 Rotate Image](ch03-arrays/rotate-image.md)
5858
- [3.7 Spiral Matrix](ch03-arrays/spiral-matrix.md)
5959
- [2.12 Closest Subsequence Sum](ch02-dynamic-programming/closest-subsequence-sum.md)
60+
61+
- [4. Linked Lists](ch04-linked-lists/index.md)
62+
- [4.0 Pattern Primer: Pointer Choreography](ch04-linked-lists/pattern-primer.md)
63+
- [4.1 Reverse Linked List](ch04-linked-lists/reverse-linked-list.md)
64+
- [4.2 Linked List Cycle](ch04-linked-lists/linked-list-cycle.md)
65+
- [4.3 Merge Two Sorted Lists](ch04-linked-lists/merge-two-sorted-lists.md)
66+
- [4.4 Remove Nth Node From End](ch04-linked-lists/remove-nth-node-from-end.md)
67+
- [4.5 Linked List Cycle II](ch04-linked-lists/linked-list-cycle-ii.md)
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Chapter 4 — Linked Lists
2+
3+
> **Source:** `src/main/kotlin/linkedlist/`
4+
>
5+
> **Master idea:** linked lists are *pointer choreography*. Every hard-looking problem is one of a handful of moves — dummy nodes, two-pointer runs, or recursion — applied twice.
6+
>
7+
> **Prerequisites:** know what a `ListNode` is (`val` + `next`). Everything else is taught here.
8+
9+
## Problems at a glance (this chapter's core set)
10+
11+
| # | Problem | Pattern | Complexity | Page |
12+
|---|---------|---------|------------|------|
13+
| 4.1 | Reverse Linked List | recursion + iteration | $O(n)$ | [→](reverse-linked-list.md) |
14+
| 4.2 | Linked List Cycle | Floyd's tortoise & hare | $O(n)$ | [→](linked-list-cycle.md) |
15+
| 4.3 | Merge Two Sorted Lists | dummy node + two pointers | $O(n+m)$ | [→](merge-two-sorted-lists.md) |
16+
| 4.4 | Remove Nth Node From End | dummy node + offset pointers | $O(n)$ | [→](remove-nth-node-from-end.md) |
17+
| 4.5 | Linked List Cycle II | Floyd's with entry-point math | $O(n)$ | [→](linked-list-cycle-ii.md) |
18+
19+
## The rest of the linkedlist/ directory
20+
21+
`src/main/kotlin/linkedlist/` holds 20+ more: Palindrome, Middle Node, Odd-Even, Swap Nodes in Pairs, Reverse Nodes in K Groups, Rotate List, Merge K Sorted Lists (heap + iterative), Add Two Numbers, Copy List with Random Pointer, Intersection of Two Linked Lists, Insert Into a Sorted Circular List, Maximum Twin Sum, and more. New pages land in the table above as they're written; the rest are cataloged in the repository's own tree.
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
# 4.5 Linked List Cycle II
2+
3+
> **Source:** [`src/main/kotlin/linkedlist/LinkedListCycle_II.kt`](https://github.com/arpanpathak/AdvancedAlgorithmPatterns/blob/main/src/main/kotlin/linkedlist/LinkedListCycle_II.kt)
4+
> **Pattern:** Floyd's with entry-point math · **Gym page — the "prove the math" favorite**
5+
6+
## The Problem
7+
8+
Like [4.2](linked-list-cycle.md), but now also return the node where the cycle **begins**. If there is no cycle, return `null`. $O(1)$ space.
9+
10+
## Examples
11+
12+
```
13+
Input: 3 -> 2 -> 0 -> -4 ─┐
14+
↑______________┘ (cycle starts at the node with value 2)
15+
Output: the node with value 2
16+
```
17+
18+
## Intuition — one more lap reveals the entry
19+
20+
Floyd's meeting ([4.2](linked-list-cycle.md)) finds *some* node in the cycle. The entry-point question is: **why does walking from the head and from the meeting point at equal speed meet at the cycle's start?**
21+
22+
Set up the notation: the non-cycle prefix has length $a$; the meeting point is $b$ nodes into the cycle (so the meeting point is $b$ steps after the entry, walking forward); the cycle has length $L$.
23+
24+
At the meeting, the tortoise has walked $a + b$ steps (it entered the cycle once and walked $b$ more). The hare walked $2(a+b)$ (twice as fast). The hare's path is also `a` (to the entry) plus some integer number $q$ of full laps plus $b$: $a + qL + b$. Equating:
25+
26+
$$
27+
2(a + b) = a + qL + b \quad\Longrightarrow\quad a + b = qL
28+
$$
29+
30+
So $a + b$ is an exact multiple of $L$. Now the key observation:
31+
32+
- A pointer starting at the **head** needs exactly $a$ steps to reach the entry.
33+
- A pointer starting at the **meeting point** walks $a \bmod L$ steps to reach some node; but $a \equiv L - b \pmod L$ (from $a + b \equiv 0$), and walking $L - b$ steps forward from a point $b$ steps into the cycle lands **exactly on the entry**.
34+
35+
Both pointers reach the cycle's start after exactly $a$ steps. Walk them in lockstep (1 step each); their first collision is the answer.
36+
37+
## Approach 1 — Hash set
38+
39+
Store every visited node; the first node seen twice is the entry. $O(n)$ time, $O(n)$ space — fails the $O(1)$-space requirement.
40+
41+
## Approach 2 — Floyd + entry-point walk (optimal)
42+
43+
```kotlin
44+
/**
45+
* @param head the head of the linked list
46+
* @return the node where the cycle begins, or null if there is no cycle
47+
*/
48+
fun detectCycle(head: ListNode?): ListNode? {
49+
var slow = head
50+
var fast = head
51+
52+
// Phase 1: find a meeting point inside the cycle (standard Floyd).
53+
while (fast != null && fast.next != null) {
54+
slow = slow?.next
55+
fast = fast.next?.next
56+
if (slow == fast) break
57+
}
58+
59+
// No cycle: the hare fell off the list.
60+
if (fast == null || fast.next == null) return null
61+
62+
// Phase 2: head-pointer and meeting-pointer walk 1 step each.
63+
// By the congruence a == L - b (mod L), they meet at the cycle start.
64+
var entry: ListNode? = head
65+
while (entry != slow) {
66+
entry = entry?.next
67+
slow = slow?.next
68+
}
69+
return entry
70+
}
71+
```
72+
73+
```java
74+
public class LinkedListCycleII {
75+
/**
76+
* @param head the head of the linked list
77+
* @return the node where the cycle begins, or null if there is no cycle
78+
*/
79+
public ListNode detectCycle(ListNode head) {
80+
ListNode slow = head, fast = head;
81+
while (fast != null && fast.next != null) { // phase 1: meet inside
82+
slow = slow.next;
83+
fast = fast.next.next;
84+
if (slow == fast) break;
85+
}
86+
if (fast == null || fast.next == null) return null; // no cycle
87+
88+
ListNode entry = head; // phase 2: walk to entry
89+
while (entry != slow) {
90+
entry = entry.next;
91+
slow = slow.next;
92+
}
93+
return entry;
94+
}
95+
}
96+
```
97+
98+
```cpp
99+
struct ListNode {
100+
int val;
101+
ListNode* next;
102+
ListNode(int x) : val(x), next(nullptr) {}
103+
};
104+
105+
class LinkedListCycleII {
106+
public:
107+
/**
108+
* @param head the head of the linked list
109+
* @return the node where the cycle begins, or null if there is no cycle
110+
*/
111+
ListNode* detectCycle(ListNode* head) {
112+
ListNode* slow = head;
113+
ListNode* fast = head;
114+
while (fast && fast->next) {
115+
slow = slow->next;
116+
fast = fast->next->next;
117+
if (slow == fast) break;
118+
}
119+
if (!fast || !fast->next) return nullptr;
120+
121+
ListNode* entry = head;
122+
while (entry != slow) {
123+
entry = entry->next;
124+
slow = slow->next;
125+
}
126+
return entry;
127+
}
128+
};
129+
```
130+
131+
```python
132+
def detect_cycle(head: ListNode | None) -> ListNode | None:
133+
"""
134+
@param head: the head of the linked list
135+
@return: the node where the cycle begins, or None if there is no cycle
136+
"""
137+
slow = fast = head
138+
while fast and fast.next: # phase 1: meet inside the cycle
139+
slow = slow.next
140+
fast = fast.next.next
141+
if slow is fast:
142+
break
143+
if fast is None or fast.next is None:
144+
return None # no cycle
145+
146+
entry = head # phase 2: walk to the entry
147+
while entry is not slow:
148+
entry = entry.next
149+
slow = slow.next
150+
return entry
151+
```
152+
153+
```rust
154+
use std::collections::HashSet;
155+
156+
impl Solution {
157+
/// @param head the head of the linked list
158+
/// @return the node where the cycle begins, or None if there is no cycle
159+
pub fn detect_cycle(head: Option<Box<ListNode>>) -> Option<Box<ListNode>> {
160+
// The O(1)-space two-pointer version needs raw-pointer gymnastics in Rust;
161+
// the readable HashSet version below is the pragmatic fallback.
162+
let mut seen: HashSet<*const ListNode> = HashSet::new();
163+
let mut cur = head.as_ref();
164+
while let Some(node) = cur {
165+
let ptr = node.as_ref() as *const ListNode;
166+
if !seen.insert(ptr) {
167+
return Some(node.clone());
168+
}
169+
cur = node.next.as_ref();
170+
}
171+
None
172+
}
173+
}
174+
```
175+
176+
> **Rust note:** the canonical two-pointer phase requires raw pointers (see [4.2](linked-list-cycle.md) for the detection-only version). When the entry node must be *returned*, the HashSet version shown above is the standard readable Rust tradeoff — name it honestly if asked about space.
177+
178+
## Dry run
179+
180+
**Input:** `3 -> 2 -> 0 -> -4`, with `-4.next = 2`. So `a = 1` (one node before the cycle), entry = the `2`, cycle length `L = 3`.
181+
182+
```
183+
Phase 1 (Floyd):
184+
slow=3 fast=3
185+
step 1: slow=2, fast=0
186+
step 2: slow=0, fast=-4
187+
step 3: slow=-4, fast=-4 -> meet at -4. Tortoise traveled 3 steps = a + b, so b = 2.
188+
189+
Phase 2 (entry walk):
190+
entry=3, slow=-4
191+
entry=2, slow=2 -> entry == slow -> return the 2 ✓
192+
```
193+
194+
Verify the math: `a + b = 1 + 2 = 3 = qL` with `q = 1` ✓. And `L − b = 1 = a`, so walking `a = 1` step from the meeting point (-4 → 2) lands on the same node as walking `a = 1` step from the head (3 → 2) — the entry. The lockstep walk found it in one step.
195+
196+
## Complexity
197+
198+
**Time.** Phase 1 is $O(n_0 + L)$; phase 2 walks at most $a$ steps:
199+
200+
$$
201+
T(n) = O(n)
202+
$$
203+
204+
**Space.** $O(1)$ (two pointers).
205+
206+
## Variants & follow-ups
207+
208+
- **[4.2](linked-list-cycle.md)** — detection only; this page adds the entry-point phase.
209+
- **Intersection of Two Linked Lists** (`src/main/kotlin/linkedlist/IntersectionOfTwoLinkedList.kt`) — the same "two walks meet at a common node" idea in a different shape.
210+
- **Interview follow-up:** "Why does the entry walk terminate?" Phase 2 pointers are both *inside-or-before* the cycle; they meet within at most $a$ steps (both reach the entry exactly then), so no infinite loop.
211+
- **Interview follow-up:** "Does the meeting point matter for phase 2?" Any meeting point works — the congruence holds for whatever `b` the race produced. That's why the algorithm is deterministic despite the "arbitrary" meeting.

0 commit comments

Comments
 (0)