Skip to content

Commit d42073f

Browse files
committed
Merge Variant Gallery into main chapters: TSP top-down functional into 17.4, LRU linked-list family into 18.1, coin/BFS/counting into 2.16, N-Queens set-based into 12.4, Eulerian trio into 17.9, WL2 forward-BFS into 6.13, LFU lean into 18.2, TreeSet median into 7.3, Kahn filter-seed into 6.3/6.8, functional/map-key/structure sections into their chapters; delete ch19-gallery; add proper pages 8.10 Basic Calculator II, 10.12 Rank Transform, 10.13 Unique Occurrences, 16.8 Pow(x,n)
1 parent 9136f2f commit d42073f

50 files changed

Lines changed: 2258 additions & 2013 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎CodingInterviewFightClub/src/SUMMARY.md‎

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@
133133
- [8.7 Remove K Digits](ch08-stacks/remove-k-digits.md)
134134
- [8.8 Decode String](ch08-stacks/decode-string.md)
135135
- [8.9 Longest Valid Parentheses](ch08-stacks/longest-valid-parentheses.md)
136+
- [8.10 Basic Calculator II](ch08-stacks/basic-calculator-ii.md)
136137

137138
- [9. Strings](ch09-strings/index.md)
138139
- [9.0 Pattern Primer: The Three Lenses](ch09-strings/pattern-primer.md)
@@ -161,6 +162,8 @@
161162
- [10.9 Count Rectangles Formed By Points](ch10-hash-tables/count-rectangles-formed-by-points.md)
162163
- [10.10 First Missing Positive](ch10-hash-tables/first-missing-positive.md)
163164
- [10.11 Subarray Sums Divisible By K](ch10-hash-tables/subarray-sums-divisible-by-k.md)
165+
- [10.12 Rank Transform Of An Array](ch10-hash-tables/rank-transform-of-an-array.md)
166+
- [10.13 Unique Number Of Occurrences](ch10-hash-tables/unique-number-of-occurrences.md)
164167

165168
- [11. Greedy](ch11-greedy/index.md)
166169
- [11.0 Pattern Primer: The Local Choice, Defended](ch11-greedy/pattern-primer.md)
@@ -231,6 +234,7 @@
231234
- [16.5 Maximum XOR Of Two Numbers](ch16-bit-manipulation/maximum-xor-of-two-numbers.md)
232235
- [16.6 Sum Of All Subset XOR Totals](ch16-bit-manipulation/sum-of-all-subset-xor-totals.md)
233236
- [16.7 Smallest Number With All Set Bits](ch16-bit-manipulation/smallest-number-with-all-set-bits.md)
237+
- [16.8 Pow(x, n)](ch16-bit-manipulation/pow-x-n.md)
234238

235239
- [17. Advanced Graphs](ch17-advanced-graphs/index.md)
236240
- [17.0 Pattern Primer: Flow, Matching, MST, and State-Space BFS](ch17-advanced-graphs/pattern-primer.md)
@@ -263,19 +267,3 @@
263267
- [Repo Coverage Index](appendix-repo-coverage-index.md) — every file in src/main/kotlin mapped
264268
- [The Roadmap](appendix-roadmap.md) — the remaining uncovered files
265269

266-
- [Chapter 19 — Variant Gallery](ch19-gallery/index.md)
267-
- [19.0 Pattern Primer: Same Algorithm, Different Costumes](ch19-gallery/pattern-primer.md)
268-
- [19.1 Travelling Salesman — Top-Down Functional](ch19-gallery/travelling-salesman-top-down.md)
269-
- [19.2 LRU Cache — Linked-List Family](ch19-gallery/lru-cache-linked-list-family.md)
270-
- [19.3 Coin Change — Four Implementations](ch19-gallery/coin-change-family.md)
271-
- [19.4 N-Queens — Set-Based Family](ch19-gallery/n-queen-family.md)
272-
- [19.5 The Shorter/Better Gallery](ch19-gallery/short-code-gallery.md)
273-
- [19.6 The Eulerian Family](ch19-gallery/eulerian-family.md)
274-
- [19.7 The Functional-Programming Gallery](ch19-gallery/functional-programming-gallery.md)
275-
- [19.8 Word Ladder II — Three Implementations](ch19-gallery/word-ladder-ii-family.md)
276-
- [19.9 LFU Cache — Three Implementations](ch19-gallery/lfu-cache-family.md)
277-
- [19.10 Sliding Window Median — TreeSet](ch19-gallery/sliding-window-median-treeset.md)
278-
- [19.11 Kahn's Filter-Seed & DFS-Expression](ch19-gallery/kahns-filter-seed.md)
279-
- [19.12 One-Expression DP Gallery](ch19-gallery/one-expression-dp-gallery.md)
280-
- [19.13 Map-Key Gallery](ch19-gallery/map-key-gallery.md)
281-
- [19.14 Structure Gallery](ch19-gallery/structure-gallery.md)

‎CodingInterviewFightClub/src/ch01-binary-search/find-peak-element.md‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,38 @@ impl Solution {
140140
}
141141
```
142142

143+
### 4. `FindPeakElementBetterSolution.kt` — boundary-safe peak
144+
145+
[1.6](../ch01-binary-search/find-peak-element.md) documents the standard binary-search peak; this file's "better" claim is **explicit boundary handling** — neighbors default to `Int.MIN_VALUE` at the edges:
146+
147+
```kotlin
148+
class FindPeakElementBetterSolution {
149+
fun findPeakElement(nums: IntArray): Int? {
150+
if (nums.isEmpty()) return null
151+
152+
var (left, right) = 0 to nums.size - 1
153+
154+
while (left < right) {
155+
val mid = left + (right - left) / 2
156+
157+
// Safely handle boundaries
158+
val leftNeighbor = if (mid > 0) nums[mid - 1] else Int.MIN_VALUE
159+
val rightNeighbor = if (mid < nums.size - 1) nums[mid + 1] else Int.MIN_VALUE
160+
161+
when {
162+
nums[mid] > leftNeighbor && nums[mid] > rightNeighbor -> return mid // peak
163+
nums[mid] < rightNeighbor -> left = mid + 1 // go right
164+
else -> right = mid // go left
165+
}
166+
}
167+
return left
168+
}
169+
}
170+
```
171+
172+
**What's cool:** the `Int.MIN_VALUE` neighbors make the boundary cells valid peaks (a single-element array's only element is a peak); the `when` reads as the three-way decision; and `Int?` return explicitly signals "empty input". The three-branch structure also avoids [1.7](../ch01-binary-search/find-peak-element-safe.md)'s separate "safe boundaries" page — this file *is* that page's idea in one method.
173+
174+
143175
## Dry run
144176

145177
**Input:** `nums = [1, 2, 3, 1]`

‎CodingInterviewFightClub/src/ch02-dynamic-programming/coin-change.md‎

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,118 @@ impl Solution {
147147
}
148148
```
149149

150+
> **Sources:** [`src/main/kotlin/array/dp/`](https://github.com/arpanpathak/AdvancedAlgorithmPatterns/tree/main/src/main/kotlin/array/dp) — `CoinChange.kt`, `CoinChangeBottomUp.kt`, `CoinChangeBFS.kt`, `CoinChange_II.kt`, `CoinChange_II_BottomUp.kt`
151+
> **Pattern:** variant gallery — one problem, four engines ([2.16](../ch02-dynamic-programming/coin-change.md) covers the bottom-up winner)
152+
153+
### The family map
154+
155+
| File | Engine | What it proves |
156+
|---|---|---|
157+
| `CoinChange.kt` | memoized top-down + `coinChangeCleanAf` bottom-up | both spellings, one file |
158+
| `CoinChangeBottomUp.kt` | bottom-up + a functional `forEach` flavor | the table fill in filter-min style |
159+
| `CoinChangeBFS.kt` | **BFS over amounts** | "fewest coins" is a shortest-path problem |
160+
| `CoinChange_II.kt` / `_BottomUp.kt` | ways-counting DP | the *counting* twin ([2.16](../ch02-dynamic-programming/coin-change.md) variants) |
161+
162+
### The BFS surprise: `CoinChangeBFS.kt`
163+
164+
The coolest of the four: "fewest coins to reach `amount`" is exactly "shortest path from `0` to `amount` in a graph where each coin is an edge `x → x + coin`". BFS finds it in `O(amount · coins)`:
165+
166+
```kotlin
167+
fun coinChangeBFS(coins: IntArray, amount: Int): Int {
168+
if (amount == 0) return 0
169+
170+
val queue = ArrayDeque<Int>().apply { add(0) }
171+
val visited = mutableSetOf(0)
172+
var steps = 0
173+
174+
while (queue.isNotEmpty()) {
175+
steps++
176+
repeat(queue.size) { // one level = one coin
177+
val current = queue.removeFirst()
178+
for (coin in coins) {
179+
val next = current + coin
180+
if (next == amount) return steps
181+
if (next < amount && visited.add(next)) {
182+
queue.add(next)
183+
}
184+
}
185+
}
186+
}
187+
return -1
188+
}
189+
```
190+
191+
**Why it's correct:** every path from 0 to `amount` uses `k` edges = `k` coins, and BFS finds the minimum hop count. **Why `visited`?** Amounts are re-reachable many ways (`0+1+1` vs `0+2`); the first visit is the fewest coins, so revisits are pruned — same as [6.14](../ch06-graphs/rotting-oranges.md)'s `fresh == INF` guard. The `steps` counter with `repeat(queue.size)` is the [5.2](../ch05-trees/binary-tree-level-order-traversal.md) level fence.
192+
193+
**When to reach for it in an interview:** when the problem is *phrased* as reachability ("can you make the amount? what's the minimum number of coins?") — the graph framing is a different intuition that some interviewers love, and it pairs beautifully with the DP as "two views of the same recurrence".
194+
195+
### The counting twin: `CoinChange_II.kt`
196+
197+
"Number of ways" flips the recurrence from `min` to `sum`, and the **loop order matters** — coins outer, amounts inner makes each combination counted once:
198+
199+
```kotlin
200+
// CoinChange_II.kt (bottom-up counting)
201+
fun change(amount: Int, coins: IntArray): Int {
202+
val dp = IntArray(amount + 1)
203+
dp[0] = 1 // one way to make 0: take nothing
204+
205+
for (coin in coins) { // coin loop OUTSIDE
206+
for (a in coin..amount) {
207+
dp[a] += dp[a - coin] // order matters: combinations, not permutations
208+
}
209+
}
210+
return dp[amount]
211+
}
212+
```
213+
214+
The coin-outer loop is the classic "count combinations vs permutations" distinction ([2.6](../ch02-dynamic-programming/partition-equal-subset-sum.md)'s ascending-vs-descending discussion is this exact subtlety). `dp[0] = 1` seeds the empty combination.
215+
216+
### The top-down in the same file: `CoinChange.kt`
217+
218+
```kotlin
219+
class CoinChange {
220+
fun coinChange(coins: IntArray, amount: Int): Int {
221+
val dp = IntArray(amount + 1) { -1 }
222+
return coinChange(coins, amount, dp).let { if (it != Int.MAX_VALUE) it else -1 }
223+
}
224+
225+
private fun coinChange(coins: IntArray, amount: Int, dp: IntArray): Int {
226+
return when {
227+
amount == 0 -> 0
228+
dp[amount] != -1 -> dp[amount]
229+
else -> {
230+
var minCoins = Int.MAX_VALUE
231+
for (coin in coins) {
232+
if (coin <= amount) {
233+
val result = coinChange(coins, amount - coin, dp)
234+
if (result != Int.MAX_VALUE) {
235+
minCoins = minOf(minCoins, 1 + result)
236+
}
237+
}
238+
}
239+
minCoins
240+
}
241+
}.also { dp[amount] = it }
242+
}
243+
}
244+
```
245+
246+
The `Int.MAX_VALUE` sentinel marks "unreachable"; the `.also { dp[amount] = it }` memoizes on every return path (including the base cases — harmless). The `-1` sentinel in the public wrapper distinguishes "impossible" from "0 coins".
247+
248+
### Dry run (BFS)
249+
250+
**Input:** `coins = [1,2,5]`, `amount = 11`.
251+
252+
```
253+
queue=[0], visited={0}, steps=0
254+
steps=1: 0 -> 1, 2, 5. queue=[1,2,5]
255+
steps=2: 1 -> 2(seen),3,6. 2 -> 3(seen),4,7. 5 -> 6(seen),7(seen),10. queue=[3,6,4,7,10]
256+
steps=3: 3 -> 4(seen),5(seen),8. 6 -> 7(seen),8(seen),11 == amount -> return 3 ✓
257+
```
258+
259+
BFS finds 11 at depth 3 (5+5+1) — the `visited` set keeps the frontier small (amounts reached cheaply are never re-expanded). The DP and BFS agree: fewest coins = 3.
260+
261+
150262
## Dry run
151263

152264
**Input:** `coins = [1,2,5]`, `amount = 11`.

‎CodingInterviewFightClub/src/ch02-dynamic-programming/frog-jump.md‎

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,43 @@ impl Solution {
209209
}
210210
```
211211

212+
### 1. `FrogJumpTopDown.kt` — the whole DP in a `getOrPut` + `any`
213+
214+
[2.8](../ch02-dynamic-programming/frog-jump.md) documents the canonical version (set of reachable jumps per stone). This file compresses it to a two-line recurrence:
215+
216+
```kotlin
217+
class FrogJumpTopDown {
218+
data class State(val pos: Int, val k: Int)
219+
220+
fun canCross(stones: IntArray): Boolean {
221+
val stoneSet = stones.toSet()
222+
val cache = mutableMapOf<State, Boolean>()
223+
224+
fun isValidJump(pos: Int, nextJump: Int) =
225+
nextJump > 0 && (pos + nextJump) in stoneSet
226+
227+
fun solve(pos: Int, k: Int): Boolean =
228+
cache.getOrPut(State(pos, k)) {
229+
pos == stones.last() || (k - 1..k + 1).any { nextJump ->
230+
isValidJump(pos, nextJump) && solve(pos + nextJump, nextJump)
231+
}
232+
}
233+
234+
return solve(0, 0)
235+
}
236+
}
237+
```
238+
239+
**What's cool:**
240+
241+
- **`(k - 1..k + 1).any { ... }`** — the three candidate jump lengths (`k-1, k, k+1`) are a *range expression*, not a loop. `any` short-circuits on the first successful jump.
242+
- **`isValidJump` as a named lambda-expression** — `nextJump > 0 && (pos + nextJump) in stoneSet`; the stone-set membership IS the boundary check (no bounds arithmetic).
243+
- **`pos == stones.last()`** — the base case is a boolean OR'd into the recurrence, not a separate branch.
244+
- **`data class State`** — `(pos, k)` hashed by the map; the [17.4](../ch17-advanced-graphs/travelling-salesman-held-karp.md) state-value idiom.
245+
246+
The imperative version's two loops (outer stones, inner jumps) are gone — the recurrence is the whole file.
247+
248+
212249
## Dry run
213250

214251
**Input:** `stones = [0, 1, 3, 5, 6, 8, 12, 17]`. Bottom-up propagation:

‎CodingInterviewFightClub/src/ch02-dynamic-programming/maximal-square.md‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,37 @@ impl Solution {
168168
}
169169
```
170170

171+
### 2. `MaximalRectangle.kt` — the histogram-stack upgrade
172+
173+
The classic "largest rectangle in a binary matrix" via **per-row histograms + the [8.5](../ch08-stacks/largest-rectangle-in-histogram.md) stack**:
174+
175+
```kotlin
176+
class MaximalRectangle {
177+
fun largestRectangleArea(heights: IntArray): Int {
178+
val stack = Stack<Int>()
179+
var (maxArea, i) = listOf(0, 0)
180+
181+
while (i <= heights.size) {
182+
val currentHeight = if (i == heights.size) 0 else heights[i] // sentinel 0 flushes
183+
184+
when {
185+
stack.isEmpty() || heights[stack.last()] <= currentHeight -> stack.add(i++)
186+
else -> {
187+
val height = heights[stack.pop()]
188+
val width = if (stack.isEmpty()) i else i - stack.peek() - 1
189+
maxArea = maxOf(maxArea, height * width)
190+
}
191+
}
192+
}
193+
return maxArea
194+
}
195+
// ... plus the per-row histogram accumulation: heights[j] = if (matrix[i][j] == '1') heights[j] + 1 else 0
196+
}
197+
```
198+
199+
**What's cool:** the `i == heights.size ? 0` sentinel flushes the stack without a post-loop; the `when` is the monotonic-stack three-way decision ([8.5](../ch08-stacks/largest-rectangle-in-histogram.md) compressed); and the row-major histogram update turns the matrix problem into repeated 1-D problems.
200+
201+
171202
## Dry run
172203

173204
**Input:** `matrix = [["1","1"],["1","1"]]`.

‎CodingInterviewFightClub/src/ch02-dynamic-programming/minimum-cost-to-cut-a-stick.md‎

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,39 @@ impl Solution {
232232
}
233233
```
234234

235+
### 2. `StoneGame.kt` — the zero-sum relative score
236+
237+
The "Current Player's Score − Opponent's Score" trick lets the DP avoid tracking turns:
238+
239+
```kotlin
240+
fun stoneGame(piles: IntArray): Boolean {
241+
// Cache stores (i to j) -> Max relative score difference for that range
242+
val cache = mutableMapOf<Pair<Int, Int>, Int>()
243+
244+
/**
245+
* Returns (Current Player's Score - Opponent's Score) for the range [i, j].
246+
* This "Relative Score" approach avoids needing to track whose turn it is.
247+
*/
248+
fun pick(i: Int, j: Int): Int = cache.getOrPut(i to j) {
249+
when (i) {
250+
j -> piles[i] // one pile left: take it all
251+
252+
// Subtract the opponent's result: the recursive call returns the
253+
// advantage for the NEXT player.
254+
else -> maxOf(
255+
piles[i] - pick(i + 1, j), // take left, subtract opponent's net gain
256+
piles[j] - pick(i, j - 1) // take right, subtract opponent's net gain
257+
)
258+
}
259+
}
260+
261+
return pick(0, piles.lastIndex) >= 0
262+
}
263+
```
264+
265+
**What's cool:** `piles[i] - pick(i+1, j)` — the *relative* score means the recursion never asks "whose turn?"; the sign flips encode it. The `when (i) { j -> ... }` base case is the single-pile boundary. This is the [2.x](../ch02-dynamic-programming/pattern-primer.md) interval-DP family ([2.10](../ch02-dynamic-programming/minimum-cost-to-cut-a-stick.md) style) with the zero-sum trick as the differentiator.
266+
267+
235268
## Dry run
236269

237270
**Input:** `n = 7`, `cuts = [1, 3, 4, 5]` → `points = [0, 1, 3, 4, 5, 7]`.

‎CodingInterviewFightClub/src/ch05-trees/binary-tree-level-order-traversal.md‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,36 @@ impl Solution {
183183
}
184184
```
185185

186+
### 3. `BinaryTreeVerticalOrderTraversal.kt` — the `data class` BFS state
187+
188+
The vertical-order BFS carries `(node, column)` — and the repo even shows the *functional DFS sketch* commented out, with `TreeMap` + `getOrPut`:
189+
190+
```kotlin
191+
// The commented-out functional DFS (the "what if" sketch):
192+
// fun dfs(node: TreeNode?, verticalIndex: Int = 0) {
193+
// if (node == null) return
194+
// val bucket = result.getOrPut(verticalIndex) { LinkedList() }
195+
// bucket.add(node.`val`)
196+
// dfs(node.left, verticalIndex - 1)
197+
// dfs(node.right, verticalIndex + 1)
198+
// }
199+
// return result.map { it.value }
200+
201+
// The BFS version uses an explicit state carrier:
202+
data class VerticalIndex(val node: TreeNode, val verticalIndex: Int)
203+
204+
fun verticalOrder(root: TreeNode?): List<List<Int>> {
205+
if (root == null) return emptyList()
206+
val result = TreeMap<Int, ArrayList<Int>>()
207+
val queue: Queue<VerticalIndex> = LinkedList()
208+
queue.offer(VerticalIndex(root, 0))
209+
// ... BFS with (node, column) pairs; TreeMap keeps columns sorted
210+
}
211+
```
212+
213+
**What's cool:** the commented DFS is the *teaching artifact* — it shows the natural (but order-incorrect) recursion before the BFS that fixes level order; `getOrPut(verticalIndex) { LinkedList() }` is the bucket-create idiom; and the `data class` state carrier is the [6.x](../ch06-graphs/pattern-primer.md) "BFS with payload" pattern.
214+
215+
186216
## Dry run
187217

188218
**Input:** the tree above.

0 commit comments

Comments
 (0)