You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit d42073f
Browse filesBrowse the repository at this point in the historyBrowse files
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.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
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.
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
+
funcoinChangeBFS(coins:IntArray, amount:Int): Int {
168
+
if (amount ==0) return0
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
+
funchange(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
+
classCoinChange {
220
+
funcoinChange(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
+
privatefuncoinChange(coins:IntArray, amount:Int, dp:IntArray): Int {
226
+
returnwhen {
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".
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.
Copy file name to clipboardExpand all lines: CodingInterviewFightClub/src/ch02-dynamic-programming/frog-jump.md
+37Lines changed: 37 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -209,6 +209,43 @@ impl Solution {
209
209
}
210
210
```
211
211
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:
-**`(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.
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.
Copy file name to clipboardExpand all lines: CodingInterviewFightClub/src/ch02-dynamic-programming/minimum-cost-to-cut-a-stick.md
+33Lines changed: 33 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -232,6 +232,39 @@ impl Solution {
232
232
}
233
233
```
234
234
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
+
funstoneGame(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
+
funpick(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.
**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.
0 commit comments