Skip to content

Commit c8e6ee9

Browse files
committed
md-file addendum: Foreword + 11 pages (K coins divisible, maximal square, earliest moment, skyline, matching subseqs, KMP, count rectangles, non-overlapping intervals, reorganize string, critical connections, weighted reservoir) + 5 better-version sections (max-in-rotated, one-row edit distance, Tarjan SCC, sweep-line rooms, task scheduler simulation)
1 parent 9a987ff commit c8e6ee9

26 files changed

Lines changed: 3091 additions & 0 deletions

‎CodingInterviewFightClub/src/SUMMARY.md‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# Summary
22

3+
[Foreword](foreword.md)
4+
35
[Introduction](intro/introduction.md)
46

57
- [How To Read This Book](intro/how-to-read.md)
@@ -46,6 +48,8 @@
4648
- [2.10 Minimum Cost To Cut A Stick](ch02-dynamic-programming/minimum-cost-to-cut-a-stick.md)
4749
- [2.11 Minimum Cost To Merge Stones](ch02-dynamic-programming/minimum-cost-to-merge-stones.md)
4850
- [2.13 Maximum Profit In Job Scheduling](ch02-dynamic-programming/maximum-profit-in-job-scheduling.md)
51+
- [2.14 Count Ways To Pick K Coins Divisible By M](ch02-dynamic-programming/count-ways-to-pick-k-coins-divisible-by-m.md)
52+
- [2.15 Maximal Square](ch02-dynamic-programming/maximal-square.md)
4953

5054
- [3. Arrays, Two Pointers & Matrices](ch03-arrays/index.md)
5155
- [3.0 Pattern Primer: Two Pointers & The Sorted-Array Dance](ch03-arrays/pattern-primer.md)
@@ -94,6 +98,7 @@
9498
- [6.8 Alien Dictionary](ch06-graphs/alien-dictionary.md)
9599
- [6.9 Redundant Connection](ch06-graphs/redundant-connection.md)
96100
- [6.10 Flood Fill](ch06-graphs/flood-fill.md)
101+
- [6.11 The Earliest Moment Everyone Became Friends](ch06-graphs/the-earliest-moment-everyone-became-friends.md)
97102

98103
- [7. Heaps & Priority Queues](ch07-heaps/index.md)
99104
- [7.0 Pattern Primer: The Lazy Sorted Structure](ch07-heaps/pattern-primer.md)
@@ -104,6 +109,7 @@
104109
- [7.5 IPO (Maximize Capital)](ch07-heaps/ipo.md)
105110
- [7.6 Meeting Rooms III](ch07-heaps/meeting-rooms-iii.md)
106111
- [7.7 Single Threaded CPU](ch07-heaps/single-threaded-cpu.md)
112+
- [7.8 The Skyline Problem](ch07-heaps/the-skyline-problem.md)
107113

108114
- [8. Stacks & Queues](ch08-stacks/index.md)
109115
- [8.0 Pattern Primer: LIFO, FIFO, and the Monotonic Stack](ch08-stacks/pattern-primer.md)
@@ -126,6 +132,8 @@
126132
- [9.6 Reverse Words In A String](ch09-strings/reverse-words-in-a-string.md)
127133
- [9.7 Validate IP Address](ch09-strings/validate-ip-address.md)
128134
- [9.8 Find The Index Of The First Occurrence (Rabin-Karp)](ch09-strings/find-the-index-of-the-first-occurrence.md)
135+
- [9.9 Number Of Matching Subsequences](ch09-strings/number-of-matching-subsequences.md)
136+
- [9.10 Find The Index Of The First Occurrence (KMP)](ch09-strings/find-the-index-of-the-first-occurrence-kmp.md)
129137

130138
- [10. Hash Tables & Sets](ch10-hash-tables/index.md)
131139
- [10.0 Pattern Primer: O(1) Lookup, Three Moves](ch10-hash-tables/pattern-primer.md)
@@ -137,6 +145,7 @@
137145
- [10.6 Design HashMap](ch10-hash-tables/design-hash-map.md)
138146
- [10.7 Roman To Integer](ch10-hash-tables/roman-to-integer.md)
139147
- [10.8 Subarray Sum Equals K](ch10-hash-tables/subarray-sum-equals-k.md)
148+
- [10.9 Count Rectangles Formed By Points](ch10-hash-tables/count-rectangles-formed-by-points.md)
140149

141150
- [11. Greedy](ch11-greedy/index.md)
142151
- [11.0 Pattern Primer: The Local Choice, Defended](ch11-greedy/pattern-primer.md)
@@ -148,6 +157,8 @@
148157
- [11.6 Task Scheduler](ch11-greedy/task-scheduler.md)
149158
- [11.7 Minimum Number Of Refueling Stops](ch11-greedy/minimum-number-of-refueling-stops.md)
150159
- [11.8 Best Time To Buy And Sell Stock II](ch11-greedy/best-time-to-buy-and-sell-stock-ii.md)
160+
- [11.9 Non-Overlapping Intervals](ch11-greedy/non-overlapping-intervals.md)
161+
- [11.10 Reorganize String](ch11-greedy/reorganize-string.md)
151162

152163
- [12. Backtracking](ch12-backtracking/index.md)
153164
- [12.0 Pattern Primer: DFS With an Undo Button](ch12-backtracking/pattern-primer.md)
@@ -215,6 +226,7 @@
215226
- [17.7 Evaluate Division](ch17-advanced-graphs/evaluate-division.md)
216227
- [17.8 Bellman-Ford](ch17-advanced-graphs/bellman-ford.md)
217228
- [17.9 Reconstruct Itinerary](ch17-advanced-graphs/reconstruct-itinerary.md)
229+
- [17.10 Critical Connections In A Network](ch17-advanced-graphs/critical-connections-in-a-network.md)
218230

219231
- [18. Design & Caches](ch18-design-caches/index.md)
220232
- [18.0 Pattern Primer: Composing Structures](ch18-design-caches/pattern-primer.md)
@@ -225,3 +237,4 @@
225237
- [18.5 Flatten Nested List Iterator](ch18-design-caches/flatten-nested-list-iterator.md)
226238
- [18.6 Design A Stack With Increment Operations](ch18-design-caches/design-a-stack-with-increment-operations.md)
227239
- [18.7 Insert Delete GetRandom O(1)](ch18-design-caches/insert-delete-getrandom.md)
240+
- [18.8 Weighted Reservoir Sampling](ch18-design-caches/weighted-reservoir-sampling.md)

‎CodingInterviewFightClub/src/ch01-binary-search/find-minimum-in-rotated-sorted-array.md‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,31 @@ left=1 right=1 -> return nums[1] = 1 ✓
179179

180180
Why the `>` comparison against `nums[right]` (not `nums[left]`)? Compare with the "find the rotation point" version that tests `nums[mid] > nums[first]`. The `right`-anchored version is safe even when the array is *not rotated at all*: in a fully sorted array `nums[mid] > nums[right]` is always false, so `right` collapses leftward onto index 0 — the minimum. The `first`-anchored version would instead collapse toward the rotation point, which is index 0 too, but it needs an extra "did we rotate?" check. Anchoring on `right` is the cleaner invariant.
181181

182+
### Follow-up: find the MAXIMUM (the rotation peak)
183+
184+
The notes' version asks the mirror question: **find the largest element** in a rotated sorted array. The same "two sorted runs" picture (the intuition above) gives a two-liner: if not rotated, the answer is the last element; else locate the **rotation point** (the minimum) with the loop above, and the maximum is the element *just before it* (wrapping around):
185+
186+
```kotlin
187+
fun findLargestInRotated(arr: IntArray): Int {
188+
require(arr.isNotEmpty()) { "Array can't be empty" }
189+
190+
// Not rotated: the largest element is the right-most one
191+
if (arr[0] < arr[arr.lastIndex]) return arr[arr.lastIndex]
192+
193+
// Find the rotation point (the minimum) with the same right-anchored loop
194+
var (left, right) = 0 to arr.lastIndex
195+
while (left < right) {
196+
val mid = left + (right - left) / 2
197+
if (arr[mid] > arr[right]) left = mid + 1 // min is in the right half
198+
else right = mid
199+
}
200+
// The max is the element just before the minimum (wrapping)
201+
return arr[(left - 1 + arr.size) % arr.size]
202+
}
203+
```
204+
205+
The `arr[0] < arr[last]` early return is the notes' "prune early" trick: in a non-rotated array, the last element is always greater than the first. Otherwise the min-index `left` from Approach 2 locates the peak by adjacency.
206+
182207
## Complexity
183208

184209
**Time.** Each iteration halves the window:
Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
# 2.14 Count Ways To Pick K Coins Divisible By M
2+
3+
> **Source:** [`src/main/kotlin/google/CountNumberOfWaysToPickKCoinsSumDivisibleByM.kt`](https://github.com/arpanpathak/AdvancedAlgorithmPatterns/blob/main/src/main/kotlin/google/CountNumberOfWaysToPickKCoinsSumDivisibleByM.kt)
4+
> **Pattern:** memoized (index, count, remainder) · **Core page**
5+
6+
## The Problem
7+
8+
Coins are numbered `0..n-1`. Count how many ways to pick **exactly k coins** such that their sum is **divisible by m** (result mod $10^9+7$).
9+
10+
- Constraints: $1 \le k \le n$; `m` fits in `Int`.
11+
12+
## Examples
13+
14+
```
15+
Input: n = 4, k = 2, m = 3 -> Output: 2 ({0,3} and {1,2} both sum to a multiple of 3)
16+
Input: n = 5, k = 3, m = 3 -> Output: 2
17+
```
18+
19+
## Intuition — the state is (index, picks left, remainder); the remainder *is* the carry
20+
21+
The count-with-a-condition DP needs three axes:
22+
23+
- `idx` — which coin we're deciding next;
24+
- `k` — how many picks remain;
25+
- `rem` — the running sum **modulo m** (the only part of the sum that matters for divisibility).
26+
27+
The recurrence is the classic pick/skip:
28+
29+
```
30+
solve(idx, k, rem):
31+
k == 0 -> 1 iff rem == 0
32+
(n - idx) < k -> 0 (not enough coins left — pruning)
33+
else -> solve(idx+1, k, rem) # skip coin idx
34+
+ solve(idx+1, k-1, (rem + idx) % m) # pick coin idx
35+
```
36+
37+
**Why does `rem` carry modulo instead of the raw sum?** Only `sum % m` decides divisibility, and `(a + b) % m` is computable from `a % m` — so the remainder is a complete summary of the sum, bounded by `m` instead of by `n·m`. That's what keeps the state space at $O(n \cdot k \cdot m)$ rather than exponential.
38+
39+
**Why the `(n - idx) < k` pruning?** If fewer coins remain than picks needed, no completion exists — the branch dies without recursion. The same "remaining resources vs remaining needs" cut as [11.2](../ch11-greedy/jump-game-ii.md)'s reachability frontier, in DP clothing.
40+
41+
**The coins are 0-indexed** (the repo's comment: `coins = [0, 1, 2, 3]`), so picking coin `idx` adds `idx` to the sum — `(rem + idx) % m`. Careful: not `idx + 1`.
42+
43+
## Approach 1 — Enumerate all C(n, k) combinations (exponential)
44+
45+
Generate every k-subset and check the sum: correct, dies at n = 20.
46+
47+
## Approach 2 — Memoized (idx, k, rem) (the repo's version, optimal)
48+
49+
```kotlin
50+
fun countWays(n: Int, k: Int, m: Int): Int {
51+
val mod = 1_000_000_007
52+
data class State(val idx: Int, val k: Int, val rem: Int)
53+
54+
val _cache = mutableMapOf<State, Int>()
55+
56+
fun solve(idx: Int, k: Int, rem: Int): Int =
57+
_cache.getOrPut(State(idx, k, rem)) {
58+
when {
59+
k == 0 -> if (rem == 0) 1 else 0
60+
// Pruning: if coins remaining (n - idx) < coins needed (k), stop
61+
(n - idx) < k || idx == n -> 0
62+
else -> {
63+
val skip = solve(idx + 1, k, rem)
64+
val pick = solve(idx + 1, k - 1, (rem + (idx % m)) % m)
65+
(skip + pick) % mod
66+
}
67+
}
68+
}
69+
70+
return solve(0, k, 0)
71+
}
72+
```
73+
74+
```java
75+
import java.util.*;
76+
77+
public class CountWaysToPickKCoinsDivisibleByM {
78+
private static final int MOD = 1_000_000_007;
79+
80+
/**
81+
* @param n number of coins (0..n-1)
82+
* @param k coins to pick
83+
* @param m divisor
84+
* @return ways to pick k coins with sum divisible by m
85+
*/
86+
public int countWays(int n, int k, int m) {
87+
Map<String, Integer> memo = new HashMap<>();
88+
return solve(0, k, 0, n, m, memo);
89+
}
90+
91+
private int solve(int idx, int k, int rem, int n, int m, Map<String, Integer> memo) {
92+
if (k == 0) return rem == 0 ? 1 : 0;
93+
if (n - idx < k || idx == n) return 0;
94+
95+
String key = idx + "," + k + "," + rem;
96+
if (memo.containsKey(key)) return memo.get(key);
97+
98+
int skip = solve(idx + 1, k, rem, n, m, memo);
99+
int pick = solve(idx + 1, k - 1, (rem + idx) % m, n, m, memo);
100+
int result = (skip + pick) % MOD;
101+
memo.put(key, result);
102+
return result;
103+
}
104+
}
105+
```
106+
107+
```cpp
108+
#include <cstring>
109+
110+
class CountWaysToPickKCoinsDivisibleByM {
111+
long long memo[31][31][31];
112+
int n, m, k;
113+
const long long MOD = 1'000'000'007LL;
114+
115+
long long solve(int idx, int left, int rem) {
116+
if (left == 0) return rem == 0 ? 1 : 0;
117+
if (n - idx < left || idx == n) return 0;
118+
if (memo[idx][left][rem] != -1) return memo[idx][left][rem];
119+
120+
long long skip = solve(idx + 1, left, rem);
121+
long long pick = solve(idx + 1, left - 1, (rem + idx) % m);
122+
return memo[idx][left][rem] = (skip + pick) % MOD;
123+
}
124+
125+
public:
126+
/**
127+
* @param n number of coins (0..n-1)
128+
* @param k coins to pick
129+
* @param m divisor
130+
* @return ways to pick k coins with sum divisible by m
131+
*/
132+
int countWays(int n, int k, int m) {
133+
this->n = n; this->k = k; this->m = m;
134+
std::memset(memo, -1, sizeof memo);
135+
return (int)solve(0, k, 0);
136+
}
137+
};
138+
```
139+
140+
```python
141+
from functools import lru_cache
142+
143+
MOD = 1_000_000_007
144+
145+
def count_ways(n: int, k: int, m: int) -> int:
146+
"""
147+
@param n: number of coins (0..n-1)
148+
@param k: coins to pick
149+
@param m: divisor
150+
@return: ways to pick k coins with sum divisible by m
151+
"""
152+
@lru_cache(None)
153+
def solve(idx: int, left: int, rem: int) -> int:
154+
if left == 0:
155+
return 1 if rem == 0 else 0
156+
if n - idx < left or idx == n:
157+
return 0 # not enough coins left — pruning
158+
159+
skip = solve(idx + 1, left, rem)
160+
pick = solve(idx + 1, left - 1, (rem + idx) % m)
161+
return (skip + pick) % MOD
162+
163+
return solve(0, k, 0)
164+
```
165+
166+
```rust
167+
use std::collections::HashMap;
168+
169+
impl Solution {
170+
/// @param n number of coins (0..n-1)
171+
/// @param k coins to pick
172+
/// @param m divisor
173+
/// @return ways to pick k coins with sum divisible by m
174+
pub fn count_ways(n: i32, k: i32, m: i32) -> i32 {
175+
const MOD: i64 = 1_000_000_007;
176+
let mut memo: HashMap<(i32, i32, i32), i64> = HashMap::new();
177+
178+
fn solve(idx: i32, left: i32, rem: i32, n: i32, m: i32,
179+
memo: &mut HashMap<(i32, i32, i32), i64>) -> i64 {
180+
if left == 0 { return if rem == 0 { 1 } else { 0 }; }
181+
if n - idx < left || idx == n { return 0; } // pruning
182+
183+
if let Some(&v) = memo.get(&(idx, left, rem)) { return v; }
184+
let skip = solve(idx + 1, left, rem, n, m, memo);
185+
let pick = solve(idx + 1, left - 1, (rem + idx) % m, n, m, memo);
186+
let v = (skip + pick) % MOD;
187+
memo.insert((idx, left, rem), v);
188+
v
189+
}
190+
191+
solve(0, k, 0, n, m, &mut memo) as i32
192+
}
193+
}
194+
```
195+
196+
## Dry run
197+
198+
**Input:** `n = 4, k = 2, m = 3` — coins {0,1,2,3}, pick 2 with `sum % 3 == 0`.
199+
200+
```
201+
enumerate (idx strictly increasing): pairs {0,3} -> 0+3=3 ✓, {1,2} -> 3 ✓.
202+
All other pairs: 0+1=1, 0+2=2, 1+3=4≡1, 2+3=5≡2 -> fail. Answer: 2.
203+
204+
DP path (abridged): solve(0,2,0)
205+
skip -> solve(1,2,0): eventually counts pairs among {1,2,3}: {1,2} ✓ -> 1
206+
pick -> solve(1,1,0%3=0): counts pairs starting with coin 0:
207+
pick coin 1 -> solve(2,0,1): rem 1 != 0 -> 0
208+
pick coin 2 -> solve(3,0,2): 0
209+
pick coin 3 -> solve(4,0,3%3=0): 1 -> the {0,3} pair ✓
210+
total = 1 + 1 = 2 ✓
211+
```
212+
213+
The remainder carry in action: picking coin 3 adds `3 % 3 = 0`, so the state `(4, 0, 0)` closes the `{0,3}` choice — the raw sum never appears, only its residue. The pruning `(n - idx) < left` kills branches like "pick 2 coins from only 1 remaining" instantly.
214+
215+
## Complexity
216+
217+
**Time.** States `n × k × m`, O(1) per state:
218+
219+
$$
220+
T(n, k, m) = O(n \cdot k \cdot m)
221+
$$
222+
223+
**Space.** The memo:
224+
225+
$$
226+
S(n, k, m) = O(n \cdot k \cdot m)
227+
$$
228+
229+
## Variants & follow-ups
230+
231+
- **Target Sum** (`array/dp/TargetSum.kt`) — the same (index, remainder-carry) counting, with a signed target instead of a modulo.
232+
- **Partition Equal Subset Sum** ([2.6](partition-equal-subset-sum.md)) — divisibility reachability without the pick-count axis.
233+
- **Interview follow-up:** "Why does `rem` make the state small?" Only `sum % m` determines divisibility, and it composes under addition — so the remainder is a lossless summary of the sum, bounded by `m` (≤ 30 here). Replace the remainder with the raw sum and the state space explodes to $n \cdot k \cdot (n \cdot m)$.

‎CodingInterviewFightClub/src/ch02-dynamic-programming/index.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
| 2.12 | Closest Subsequence Sum | meet-in-the-middle (see [1.22](../ch01-binary-search/closest-subsequence-sum.md)) | $O(2^{n/2} \log 2^{n/2})$ | [→](../ch01-binary-search/closest-subsequence-sum.md) |
2525
| 2.13 | Maximum Profit In Job Scheduling | sort + `dp[i]` = max profit up to job i | $O(n \log n)$ | [→](maximum-profit-in-job-scheduling.md) |
2626

27+
| 2.14 | Count Ways To Pick K Coins Divisible By M | memoized (idx, k, rem) | $O(nkm)$ | [→](count-ways-to-pick-k-coins-divisible-by-m.md) |
28+
| 2.15 | Maximal Square | min-of-three DP | $O(mn)$ | [→](maximal-square.md) |
2729
## Reading order
2830

2931
2.2 and 2.3 first — they're the *ur-examples* of the state-shape `dp[i][j]`. Then 2.1 (same table, different recurrence), then the knapsack family (2.4–2.6) which is the most frequently re-appearing pattern in real interviews, then the interval DPs (2.10, 2.11), then the gyms (2.8, 2.9, 2.13). End with 2.12 which is the *anti-DP* — it proves you know when **not** to reach for a DP table.

0 commit comments

Comments
 (0)