Repository navigation
Expand file tree
/
Copy pathdpPatterns.ts
More file actions
4516 lines (4264 loc) Β· 213 KB
/
Copy pathdpPatterns.ts
File metadata and controls
4516 lines (4264 loc) Β· 213 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/**
* ============================================================================
* DYNAMIC PROGRAMMING PATTERN PROBLEMS
* ============================================================================
*
* Core Insight: Solve complex problems by breaking into overlapping subproblems
*
* ============================================================================
* π― PATTERN RECOGNITION - When to use DP?
* ============================================================================
*
* 1. OPTIMAL SUBSTRUCTURE
* β Optimal solution contains optimal solutions to subproblems
* β "Best way to X" depends on "best way to smaller X"
*
* 2. OVERLAPPING SUBPROBLEMS
* β Same subproblem solved multiple times
* β Without memoization: exponential time
* β With memoization: polynomial time
*
* 3. KEYWORDS TO LOOK FOR:
* β "How many ways...?" (counting)
* β "Minimum/Maximum...?" (optimization)
* β "Is it possible...?" (feasibility)
* β "Longest/Shortest...?" (extremes)
*
* ============================================================================
* π§ DP APPROACHES
* ============================================================================
*
* 1. TOP-DOWN (Memoization)
* β Start from main problem, recurse to subproblems
* β Cache results to avoid recomputation
* β More intuitive, but uses call stack
*
* 2. BOTTOM-UP (Tabulation)
* β Start from smallest subproblems, build up
* β Usually more space-efficient
* β Often can optimize to O(1) space
*
* ============================================================================
* πͺ THE FOREVER RECIPE β 5 questions that write ANY DP for you
* ============================================================================
*
* You do NOT need to "spot the trick". You answer these five questions, in
* this order, out loud, every single time. The answers ARE the code. Every
* problem in this file has a "RECIPE APPLIED" block showing its five answers,
* so you can practise the same five questions 30+ times.
*
* Q1. WHAT AM I CHOOSING? β find the choice
* At each step, what are my options? "Take this coin, or skip it."
* "Climb 1 stair, or 2." "Match these letters, or delete one."
* If you cannot name a choice, it is probably not a DP problem.
*
* Q2. WHAT DO I HAVE TO REMEMBER? β the STATE
* To know where I am, how many numbers do I need? "Which index I'm at"
* is one number. "Which index AND how much budget is left" is two.
* β HOW MANY NUMBERS YOU MUST REMEMBER = HOW MANY DIMENSIONS YOUR TABLE
* HAS. One number β dp[i]. Two β dp[i][j]. That is the whole rule.
*
* Q3. HOW DOES A BIGGER ANSWER USE SMALLER ANSWERS? β the TRANSITION
* Write ONE line: dp[i] = <something using dp[smaller than i]>.
* This line is usually just Q1's choices with a min/max/+ around them.
*
* Q4. WHAT IS THE SMALLEST ANSWER I KNOW FOR FREE? β the BASE CASE
* "Zero stairs = 1 way." "Empty string = 0 edits." Get this wrong and
* everything above it is wrong, so say it before you write any loop.
*
* Q5. IN WHAT ORDER DO I FILL THE TABLE? β the ORDER
* Every cell must be filled AFTER the cells it reads. If dp[i] reads
* dp[i-1], go leftβright. If it reads dp[i+1], go rightβleft.
*
* Then, where do I write the answer? β usually dp[n] or dp[0][n-1]. Say it
* out loud too, because "the answer is the last cell" is not always true
* (sometimes it is the max over the whole table β e.g. Maximal Square).
*
* ----------------------------------------------------------------------------
* THE 4-RUNG LADDER β how to get from "no idea" to optimal, without a leap
* ----------------------------------------------------------------------------
* Never jump straight to the clever loop. Climb:
*
* RUNG 1 BRUTE FORCE recursion. Just encode Q1's choice. It is exponential
* and that is FINE β it is correct, and it is your reference answer.
* RUNG 2 NAME THE WASTE. Point at one specific subproblem that gets
* computed twice ("ways(3) is computed on both sides"). This single
* sentence is what earns you the right to add a cache.
* RUNG 3 MEMOIZE. Add a cache to RUNG 1. Usually a 3-line change, and it
* turns exponential into polynomial without changing your thinking.
* RUNG 4 TABULATE (loops instead of recursion), then LOOK AT HOW FEW CELLS
* YOU ACTUALLY READ. If dp[i] only reads dp[i-1] and dp[i-2], throw
* the array away and keep two variables β O(1) space.
*
* In an interview: RUNG 1 + RUNG 2 out loud, RUNG 3 on the board (fastest way
* to a CORRECT solution), then offer RUNG 4 as the follow-up optimization.
*
* β IF YOU REMEMBER NOTHING ELSE:
* FIND THE CHOICE β NAME WHAT YOU MUST REMEMBER β WRITE ONE LINE OF
* RECURRENCE β CACHE IT β THEN SHRINK THE TABLE.
*
* ============================================================================
* π COMMON DP CATEGORIES
* ============================================================================
*
* | Category | Examples | Typical Recurrence |
* |-------------------|---------------------------------|-----------------------|
* | 1D Linear | Climbing Stairs, House Robber | dp[i] = f(dp[i-1], dp[i-2]) |
* | 2D Grid | Unique Paths, Min Path Sum | dp[i][j] = f(up, left) |
* | String | LCS, Edit Distance, Word Break | dp[i][j] = f(chars match?) |
* | Knapsack | Coin Change, Subset Sum | dp[i] = f(include/exclude) |
* | Interval | Matrix Chain, Burst Balloons | dp[i][j] = f(split point k) |
*
* Time Complexity: Usually O(n), O(nΒ²), or O(nΒ³)
* Space Complexity: O(n) or O(nΒ²), often optimizable
*/
/**
* ============================================================================
* 1. Climbing Stairs (LeetCode 70) - THE CLASSIC DP INTRO
* ============================================================================
*
* You are climbing a staircase with n steps. Each time you can climb 1 or 2 steps.
* How many distinct ways can you reach the top?
*
* ============================================================================
* πͺ RECIPE APPLIED (the five questions from the file header)
* ============================================================================
*
* Q1 CHOICE Your LAST move was either a 1-stair step or a 2-stair step.
* That is it. Two options, nothing else to decide.
* Q2 STATE "Which stair am I standing on" β ONE number β 1D dp[i].
* Q3 TRANSITION dp[i] = dp[i-1] + dp[i-2]
* β Note the PLUS. We are COUNTING ways, so the two options are
* ADDED. If the question asked for the cheapest climb we would
* take a min instead. Counting β add; optimising β min/max.
* Q4 BASE CASE dp[1] = 1, dp[2] = 2. (Say these out loud before looping.)
* Q5 ORDER i = 3 upward, because dp[i] reads smaller i.
* ANSWER dp[n] β held in `prev1` after the roll to O(1) space.
*
* ============================================================================
* π REAL-WORLD SCENARIOS
* ============================================================================
*
* 1. DECISION PATHS: How many ways to complete a multi-step process
* - Payment options: pay in 1 or 2 installments
* - Route planning: take 1 or 2 stops at a time
*
* 2. FIBONACCI IN DISGUISE: This IS the Fibonacci sequence!
* - n=1: 1 way (1)
* - n=2: 2 ways (1+1, 2)
* - n=3: 3 ways (1+1+1, 1+2, 2+1) = fib(3)
* - n=4: 5 ways = fib(4)
*
* ============================================================================
* VISUALIZATION - Building up from smaller problems
* ============================================================================
*
* n = 5 stairs
*
* π TOP (step 5)
* βββββ
* βββ€ 5 β ways(5) = ways(4) + ways(3) = 5 + 3 = 8
* β βββββ
* β βββββ
* βββ€ 4 β ways(4) = ways(3) + ways(2) = 3 + 2 = 5
* β βββββ
* β βββββ
* βββ€ 3 β ways(3) = ways(2) + ways(1) = 2 + 1 = 3
* β βββββ
* β βββββ
* βββ€ 2 β ways(2) = 2 (either 1+1 or 2)
* β βββββ
* β βββββ
* βββ€ 1 β ways(1) = 1 (only one way: take 1 step)
* βββββ
* πΆ START
*
* Step-by-step for n=4:
* ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
* β Step 1: ways[1] = 1 β
* β Step 2: ways[2] = 2 β
* β Step 3: ways[3] = ways[2] + ways[1] = 2 + 1 = 3 β
* β Step 4: ways[4] = ways[3] + ways[2] = 3 + 2 = 5 β¨ β
* ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
*
* The 5 ways to climb 4 stairs:
* 1. 1+1+1+1 2. 1+1+2 3. 1+2+1 4. 2+1+1 5. 2+2
*
* ============================================================================
* π KEY INSIGHT: Why does this work?
* ============================================================================
*
* To reach step n, you MUST have come from either:
* - Step n-1 (took 1 step), OR
* - Step n-2 (took 2 steps)
*
* So: ways(n) = ways(n-1) + ways(n-2)
*
* This IS the Fibonacci recurrence! π―
*
* ============================================================================
* SPACE OPTIMIZATION: O(n) β O(1)
* ============================================================================
*
* We only need the previous TWO values, not the entire array!
*
* Instead of: dp = [1, 2, 3, 5, 8, ...]
* We use: prev2, prev1, current (just 3 variables)
*
* ============================================================================
* COMPLEXITY
* ============================================================================
* Time: O(n) - single pass
* Space: O(1) - only 2 variables (optimized from O(n))
*/
/**
* ----------------------------------------------------------------------------
* STEP 1 β BRUTE FORCE (the rung before `climbStairs` below)
* ----------------------------------------------------------------------------
* Encode the choice and nothing else: from step n you arrived from n-1 or n-2.
* Correct, and unusable past n β 40.
*
* NAME THE WASTE: ways(5) calls ways(4) and ways(3); ways(4) calls ways(3)
* AGAIN. The whole left subtree of ways(3) is recomputed from scratch. That
* single observation β an overlapping subproblem β is what licenses a cache.
*
* @example
* climbStairsBruteForce(5); // 8
*
* Time: O(2βΏ). Space: O(n) call stack.
*/
export function climbStairsBruteForce(n: number): number {
// Same base cases as the canonical `climbStairs` below: 1 step = 1 way,
// 2 steps = 2 ways. (LeetCode guarantees n >= 1.)
if (n <= 2) return n;
return climbStairsBruteForce(n - 1) + climbStairsBruteForce(n - 2);
}
/**
* ----------------------------------------------------------------------------
* STEP 2 β MEMOIZE (same recursion, one cache β O(2βΏ) β O(n))
* ----------------------------------------------------------------------------
* @example
* climbStairsMemo(45); // 1836311903
*
* Time: O(n). Space: O(n).
*/
export function climbStairsMemo(n: number): number {
const memo = new Map<number, number>();
function ways(step: number): number {
if (step <= 2) return step; // matches `climbStairs` exactly
if (memo.has(step)) return memo.get(step)!;
const total = ways(step - 1) + ways(step - 2);
memo.set(step, total);
return total;
}
return ways(n);
}
/**
* STEP 3 β TABULATE + ROLL TO O(1) β
OPTIMAL (below).
*/
export function climbStairs(n: number): number {
// Base cases: 1 step = 1 way, 2 steps = 2 ways
if (n <= 2) return n;
// Only need previous two values (space optimization)
let prev2 = 1; // ways to reach step 1
let prev1 = 2; // ways to reach step 2
// Build up from step 3 to step n
for (let i = 3; i <= n; i++) {
const current = prev1 + prev2; // ways(i) = ways(i-1) + ways(i-2)
prev2 = prev1; // shift window
prev1 = current;
}
return prev1;
}
/**
* ============================================================================
* 2. House Robber (LeetCode 198) - INCLUDE/EXCLUDE PATTERN
* ============================================================================
*
* You're a robber planning to rob houses along a street. Adjacent houses have
* connected security systems - robbing two adjacent houses triggers the alarm.
* Return the maximum amount you can rob without alerting the police.
*
* ============================================================================
* πͺ RECIPE APPLIED (the five questions from the file header)
* ============================================================================
*
* Q1 CHOICE At house i: ROB it (then you must have skipped i-1, so add the
* best from i-2), or SKIP it (keep the best from i-1).
* Q2 STATE "Which house am I deciding on" β ONE number β 1D dp[i].
* Q3 TRANSITION dp[i] = max(dp[i-1], dp[i-2] + nums[i])
* β skip β βββββ rob βββββ
* Q4 BASE CASE dp[0] = nums[0], dp[1] = max(nums[0], nums[1]).
* Q5 ORDER i = 2 upward.
* ANSWER dp[n-1] β held in `prev1`.
*
* β THE TAKE-OR-SKIP SHAPE: this exact `max(skip, take + dp[two back])` line
* reappears in 0/1 knapsack (#22), Partition Equal Subset Sum (#23) and Target
* Sum (#24). Learn it here, where it is only one line long.
*
* ============================================================================
* π REAL-WORLD SCENARIOS
* ============================================================================
*
* 1. SCHEDULING CONFLICTS:
* - Select non-overlapping jobs for maximum profit
* - Choose talks at a conference (can't attend back-to-back)
*
* 2. RESOURCE ALLOCATION:
* - Assign tasks where consecutive tasks can't be done together
* - Rest days between workouts (can't do intense training daily)
*
* 3. ADVERTISING:
* - Place ads with minimum gap between impressions
* - Select billboards along highway (minimum distance apart)
*
* ============================================================================
* VISUALIZATION - The Decision at Each House
* ============================================================================
*
* Houses: [2, 7, 9, 3, 1]
*
* π π π π π
* $2 $7 $9 $3 $1
* 0 1 2 3 4
*
* At each house, we ask: "Rob this house, or skip it?"
*
* βββββββββ¬ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
* β House β Decision β
* βββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
* β 0 β Rob it! Best so far: $2 β
* β β dp[0] = 2 β
* βββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
* β 1 β Rob $7 (skip house 0) OR skip (keep $2)? β
* β β max(7, 2) = 7 β
* β β dp[1] = 7 β
* βββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
* β 2 β Rob $9 + dp[0]=$2 = $11 OR skip (keep dp[1]=$7)? β
* β β max(9+2, 7) = max(11, 7) = 11 β¨ β
* β β dp[2] = 11 β
* βββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
* β 3 β Rob $3 + dp[1]=$7 = $10 OR skip (keep dp[2]=$11)? β
* β β max(3+7, 11) = max(10, 11) = 11 β
* β β dp[3] = 11 β
* βββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
* β 4 β Rob $1 + dp[2]=$11 = $12 OR skip (keep dp[3]=$11)? β
* β β max(1+11, 11) = max(12, 11) = 12 β¨ β
* β β dp[4] = 12 β
* βββββββββ΄ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
*
* Answer: $12 (rob houses 0, 2, 4: $2 + $9 + $1 = $12)
*
* Verify against the alternatives:
* $2 + $9 + $1 = $12 β best
* $7 + $3 = $10
* $2 + $9 = $11
* So $12 is correct β¨
*
* ============================================================================
* π KEY INSIGHT: The Include/Exclude Pattern
* ============================================================================
*
* At each house i, you have exactly TWO choices:
*
* 1. ROB this house: Take nums[i] + best from 2 houses ago (dp[i-2])
* (Can't take dp[i-1] because that's adjacent!)
*
* 2. SKIP this house: Keep the best from previous house (dp[i-1])
*
* Recurrence: dp[i] = max(dp[i-1], dp[i-2] + nums[i])
*
* "Best up to house i" = max("Skip this house", "Rob this house")
*
* ============================================================================
* SPACE OPTIMIZATION: O(n) β O(1)
* ============================================================================
*
* Only need previous TWO values, not entire array:
* - prev2 = best up to 2 houses ago
* - prev1 = best up to previous house
*
* ============================================================================
* COMPLEXITY
* ============================================================================
* Time: O(n) - single pass
* Space: O(1) - only 2 variables
*/
export function rob(nums: number[]): number {
if (nums.length === 0) return 0;
if (nums.length === 1) return nums[0];
// prev2 = best from 2 houses ago, prev1 = best from previous house
let prev2 = nums[0];
let prev1 = Math.max(nums[0], nums[1]);
for (let i = 2; i < nums.length; i++) {
// Choice: skip this house (prev1) OR rob it (prev2 + nums[i])
const current = Math.max(prev1, prev2 + nums[i]);
prev2 = prev1;
prev1 = current;
}
return prev1;
}
/**
* ============================================================================
* 3. House Robber II (LeetCode 213) - CIRCULAR DP
* ============================================================================
*
* Same as House Robber, but houses are arranged in a CIRCLE.
* First and last houses are adjacent - can't rob both!
*
* ============================================================================
* πͺ RECIPE APPLIED (the five questions from the file header)
* ============================================================================
*
* Q1 CHOICE Same rob-or-skip as #2, plus ONE decision made before any
* looping: the first and last house are now neighbours, so at
* most one of them can be robbed.
* Q2 STATE β Unchanged from #2 β and that is the whole point. Rather than
* inventing a circular state, you REDUCE to the problem you have
* already solved: run the linear robber twice.
* Q3 TRANSITION Identical to #2, applied to two ranges:
* range A = houses 0 β¦ n-2 (exclude the last)
* range B = houses 1 β¦ n-1 (exclude the first)
* Q4 BASE CASE Same as #2 within each range; n <= 2 handled directly.
* Q5 ORDER Same as #2, once per range.
* ANSWER max(rangeA, rangeB).
*
* β TURNING A NEW PROBLEM INTO ONE YOU OWN is the most valuable move in this
* whole file β worth more than any single recurrence.
*
* ============================================================================
* π REAL-WORLD SCENARIOS
* ============================================================================
*
* 1. CIRCULAR SEATING:
* - Assign alternating seats at a round table
* - Schedule shifts for circular manufacturing line
*
* 2. RING NETWORKS:
* - Select non-adjacent nodes in a ring topology
* - Place base stations with minimum interference
*
* ============================================================================
* VISUALIZATION - The Circular Problem
* ============================================================================
*
* Houses in a circle: [2, 3, 2]
*
* π House 0 ($2)
* β± β²
* β± CIRCLE β²
* β± β²
* π House 2 ($2) βββ π House 1 ($3)
*
* Problem: If we rob House 0, we CAN'T rob House 2 (they're neighbors!)
*
* ============================================================================
* π KEY INSIGHT: Break the Circle into Two Linear Problems
* ============================================================================
*
* Since first and last are adjacent, we can't rob BOTH.
* So we solve TWO separate linear problems:
*
* 1. INCLUDE first house, EXCLUDE last: rob houses [0, 1, ..., n-2]
* 2. EXCLUDE first house, INCLUDE last: rob houses [1, 2, ..., n-1]
*
* Answer = max(case1, case2)
*
* Example: [2, 3, 2]
*
* Case 1: Rob [2, 3] (exclude last)
* β max(2, 3) = 3
*
* Case 2: Rob [3, 2] (exclude first)
* β max(3, 2) = 3
*
* Answer: max(3, 3) = 3 β¨
*
* ============================================================================
* VISUALIZATION - Breaking the Circle
* ============================================================================
*
* Original: π βπ βπ βπ βπ
* 0 1 2 3 4
* βββββββββββββββ (0 and 4 are connected!)
*
* Case 1: π βπ βπ βπ (houses 0-3, exclude house 4)
* 0 1 2 3
*
* Case 2: π βπ βπ βπ (houses 1-4, exclude house 0)
* 1 2 3 4
*
* ============================================================================
* COMPLEXITY
* ============================================================================
* Time: O(n) - two linear passes
* Space: O(1) - only 2 variables per pass
*/
export function rob2(nums: number[]): number {
if (nums.length === 0) return 0;
if (nums.length === 1) return nums[0];
if (nums.length === 2) return Math.max(nums[0], nums[1]);
// Case 1: Rob houses 0 to n-2 (include first, exclude last)
const robFirst = robHelper(nums, 0, nums.length - 2);
// Case 2: Rob houses 1 to n-1 (exclude first, include last)
const robLast = robHelper(nums, 1, nums.length - 1);
// Take the better of the two cases
return Math.max(robFirst, robLast);
}
/**
* Helper: Standard linear house robber on a subarray
* @param nums - house values
* @param start - start index (inclusive)
* @param end - end index (inclusive)
*/
function robHelper(nums: number[], start: number, end: number): number {
let prev2 = 0;
let prev1 = 0;
for (let i = start; i <= end; i++) {
const current = Math.max(prev1, prev2 + nums[i]);
prev2 = prev1;
prev1 = current;
}
return prev1;
}
/**
* ============================================================================
* 4. Longest Increasing Subsequence (LeetCode 300) - CLASSIC SUBSEQUENCE DP
* ============================================================================
*
* Given an integer array nums, return the length of the longest strictly
* increasing subsequence. (Subsequence = not necessarily contiguous!)
*
* ============================================================================
* πͺ RECIPE APPLIED (the five questions from the file header)
* ============================================================================
*
* Q1 CHOICE For each earlier index j < i with nums[j] < nums[i]: extend
* the run that ENDS at j, or do not.
* Q2 STATE β dp[i] = the length of the longest increasing run that ENDS
* EXACTLY AT i. Anchoring the state at "ending here" is what
* makes the transition a simple scan of earlier indices. The same
* trick powers Kadane (#14) and Maximal Square (#28).
* Q3 TRANSITION dp[i] = max(dp[i], dp[j] + 1) for every valid j.
* Q4 BASE CASE dp[i] = 1 β any single element is a run of length 1.
* Q5 ORDER i upward; j scans everything before i.
* ANSWER β οΈ NOT dp[n-1]. The longest run can end anywhere, so the answer
* is the MAX OVER THE WHOLE dp ARRAY. Reaching for the last cell
* out of habit is the classic wrong answer here.
*
* π See #32 (`lengthOfLISPatience`) for the O(n log n) follow-up.
*
* ============================================================================
* π REAL-WORLD SCENARIOS
* ============================================================================
*
* 1. STOCK TRADING:
* - Find longest sequence of increasing stock prices
* - Identify sustained growth periods
*
* 2. VERSION CONTROL:
* - Find longest chain of compatible software versions
* - Dependency resolution (each depends on lower version)
*
* 3. CAREER PROGRESSION:
* - Longest sequence of promotions (salary increases)
* - Project milestones with increasing complexity
*
* 4. SCHEDULING:
* - Chain of tasks where each needs more resources than previous
* - Stacking boxes (each bigger than the one below)
*
* ============================================================================
* VISUALIZATION - Step by Step
* ============================================================================
*
* nums = [10, 9, 2, 5, 3, 7, 101, 18]
*
* dp[i] = length of LIS ending at index i
*
* ββββββββ¬ββββββββ¬ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
* β i βnums[i]β Calculation β
* ββββββββΌββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
* β 0 β 10 β dp[0] = 1 (just itself) β
* ββββββββΌββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
* β 1 β 9 β Check j=0: 10 > 9? No, can't extend β
* β β β dp[1] = 1 β
* ββββββββΌββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
* β 2 β 2 β Check j=0,1: None < 2 β
* β β β dp[2] = 1 β
* ββββββββΌββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
* β 3 β 5 β Check j=0: 10 < 5? No β
* β β β Check j=1: 9 < 5? No β
* β β β Check j=2: 2 < 5? YES! dp[3] = dp[2] + 1 = 2 β¨ β
* β β β dp[3] = 2 (subsequence: [2, 5]) β
* ββββββββΌββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
* β 4 β 3 β Check j=2: 2 < 3? YES! dp[4] = dp[2] + 1 = 2 β
* β β β dp[4] = 2 (subsequence: [2, 3]) β
* ββββββββΌββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
* β 5 β 7 β Check j=2: 2 < 7? YES! dp[5] = max(1, dp[2]+1) = 2 β
* β β β Check j=3: 5 < 7? YES! dp[5] = max(2, dp[3]+1) = 3 β¨ β
* β β β Check j=4: 3 < 7? YES! dp[5] = max(3, dp[4]+1) = 3 β
* β β β dp[5] = 3 (subsequence: [2, 5, 7] or [2, 3, 7]) β
* ββββββββΌββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
* β 6 β 101 β All previous < 101, best is dp[5] + 1 = 4 β¨ β
* β β β dp[6] = 4 (subsequence: [2, 5, 7, 101]) β
* ββββββββΌββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
* β 7 β 18 β Check j=5: 7 < 18? YES! dp[7] = dp[5] + 1 = 4 β
* β β β dp[7] = 4 (subsequence: [2, 5, 7, 18]) β
* ββββββββ΄ββββββββ΄ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
*
* Final dp = [1, 1, 1, 2, 2, 3, 4, 4]
* Answer: max(dp) = 4
*
* One LIS: [2, 5, 7, 101] or [2, 3, 7, 18] or [2, 5, 7, 18]
*
* ============================================================================
* π KEY INSIGHT: "What's the best I can extend?"
* ============================================================================
*
* For each element, look at ALL previous elements:
* - If nums[j] < nums[i], we CAN extend the subsequence ending at j
* - Take the maximum: dp[i] = max(dp[i], dp[j] + 1)
*
* Recurrence: dp[i] = 1 + max(dp[j]) for all j < i where nums[j] < nums[i]
*
* ============================================================================
* COMPLEXITY
* ============================================================================
* Time: O(nΒ²) - nested loops (can be O(n log n) with binary search!)
* Space: O(n) - dp array
*
* Note: O(n log n) solution uses binary search with a "tails" array
*/
export function lengthOfLIS(nums: number[]): number {
if (nums.length === 0) return 0;
// dp[i] = length of LIS ending at index i
const dp = new Array(nums.length).fill(1);
let maxLength = 1;
for (let i = 1; i < nums.length; i++) {
// Check all previous elements
for (let j = 0; j < i; j++) {
// Can we extend the subsequence ending at j?
if (nums[j] < nums[i]) {
dp[i] = Math.max(dp[i], dp[j] + 1);
}
}
maxLength = Math.max(maxLength, dp[i]);
}
return maxLength;
}
/**
* ============================================================================
* 5. Longest Common Subsequence (LeetCode 1143) - 2D STRING DP
* ============================================================================
*
* Given two strings, return the length of their longest common subsequence.
* A subsequence is a sequence derived by deleting some (or no) elements
* without changing the order of remaining elements.
*
* ============================================================================
* πͺ RECIPE APPLIED (the five questions from the file header)
* ============================================================================
*
* Q1 CHOICE Look at the last character of each prefix. If they MATCH,
* consume both and add 1. If they do not, you must give up one
* character β try dropping it from the first string, then from
* the second, and keep whichever pays better.
* Q2 STATE "How much of string 1 have I used" AND "how much of string 2"
* β TWO numbers β 2D dp[i][j].
* Q3 TRANSITION match β dp[i][j] = dp[i-1][j-1] + 1
* no match β dp[i][j] = max(dp[i-1][j], dp[i][j-1])
* Q4 BASE CASE dp[0][*] = dp[*][0] = 0 β an empty string shares nothing.
* (The table is (m+1)x(n+1) so row/column 0 IS the base case and
* needs no special-casing inside the loop. Padding a DP table by
* one is a habit worth copying.)
* Q5 ORDER i and j both upward.
* ANSWER dp[m][n].
*
* β οΈ THE OFF-BY-ONE: dp indices are 1-based over a 0-based string, so the
* characters compared are text1[i-1] and text2[j-1], never text1[i].
*
* ============================================================================
* π REAL-WORLD SCENARIOS
* ============================================================================
*
* 1. DIFF TOOLS (git diff):
* - Find common lines between two file versions
* - LCS shows what stayed the same; the rest is additions/deletions
*
* 2. DNA SEQUENCE ALIGNMENT:
* - Find common genetic sequences between species
* - Identify evolutionary relationships
*
* 3. PLAGIARISM DETECTION:
* - Find common subsequences between documents
* - Longer LCS = more similarity
*
* 4. SPELL CHECKING:
* - Suggest corrections based on longest common parts
* - "recieve" vs "receive" - LCS helps measure similarity
*
* ============================================================================
* VISUALIZATION - 2D DP Table
* ============================================================================
*
* text1 = "abcde", text2 = "ace"
*
* Build a 2D table where dp[i][j] = LCS of text1[0..i-1] and text2[0..j-1]
*
* "" a c e
* βββββββ¬ββββββ¬ββββββ¬ββββββ
* "" β 0 β 0 β 0 β 0 β
* βββββββΌββββββΌββββββΌββββββ€
* a β 0 β 1 β 1 β 1 β β 'a' matches 'a'
* βββββββΌββββββΌββββββΌββββββ€
* b β 0 β 1 β 1 β 1 β β 'b' doesn't match, carry forward
* βββββββΌββββββΌββββββΌββββββ€
* c β 0 β 1 β 2 β 2 β β 'c' matches 'c' β 1+1=2
* βββββββΌββββββΌββββββΌββββββ€
* d β 0 β 1 β 2 β 2 β β 'd' doesn't match
* βββββββΌββββββΌββββββΌββββββ€
* e β 0 β 1 β 2 β 3 β β 'e' matches 'e' β 2+1=3 β¨
* βββββββ΄ββββββ΄ββββββ΄ββββββ
*
* Answer: dp[5][3] = 3
* The LCS is "ace"
*
* ============================================================================
* π KEY INSIGHT: Match or Skip
* ============================================================================
*
* At each cell (i, j), we compare text1[i-1] with text2[j-1]:
*
* IF characters MATCH:
* dp[i][j] = dp[i-1][j-1] + 1
* (Extend the LCS we had before this character)
*
* IF characters DON'T MATCH:
* dp[i][j] = max(dp[i-1][j], dp[i][j-1])
* (Take the better of: skip char from text1, or skip char from text2)
*
* Visual of the decision:
*
* dp[i-1][j-1] dp[i-1][j]
* β β
* β² β
* dp[i][j-1] β dp[i][j]
*
* ============================================================================
* COMPLEXITY
* ============================================================================
* Time: O(m Γ n) - fill the 2D table
* Space: O(m Γ n) - the 2D table (can optimize to O(min(m,n)))
*/
export function longestCommonSubsequence(text1: string, text2: string): number {
const m = text1.length;
const n = text2.length;
// dp[i][j] = LCS of text1[0..i-1] and text2[0..j-1]
// Extra row/col for empty string base case
const dp = Array(m + 1)
.fill(null)
.map(() => Array(n + 1).fill(0));
for (let i = 1; i <= m; i++) {
for (let j = 1; j <= n; j++) {
if (text1[i - 1] === text2[j - 1]) {
// Characters match! Extend the LCS
dp[i][j] = dp[i - 1][j - 1] + 1;
} else {
// No match - take the better of skipping either character
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
return dp[m][n];
}
/**
* ============================================================================
* 6. Word Break (LeetCode 139) - STRING SEGMENTATION DP
* ============================================================================
*
* Given a string s and a dictionary wordDict, return true if s can be
* segmented into space-separated dictionary words.
*
* ============================================================================
* πͺ RECIPE APPLIED (the five questions from the file header)
* ============================================================================
*
* Q1 CHOICE "Where does the LAST word start?" Try every split point j and
* ask: is the front part s[0..j] already known-breakable AND is
* the remaining chunk s[j..i] a dictionary word?
* Q2 STATE "How many characters have I consumed" β ONE number β 1D
* dp[i], and it holds a BOOLEAN, not a count.
* Q3 TRANSITION dp[i] = OR over all j < i of (dp[j] && s.slice(j,i) in dict)
* Feasibility questions ("can it be done") use OR the way
* counting questions use + and optimisation uses min/max.
* Q4 BASE CASE dp[0] = true β the empty string is trivially breakable. This
* seed is what every other cell is ultimately built on.
* Q5 ORDER i upward from 1; every j is smaller, so already decided.
* ANSWER dp[s.length].
*
* The `break` in the inner loop is not an optimisation of the logic β once one
* valid split is found, dp[i] is already true and nothing can improve on it.
*
* ============================================================================
* π REAL-WORLD SCENARIOS
* ============================================================================
*
* 1. TEXT SEGMENTATION:
* - Break concatenated text into words (no spaces)
* - Example: "ilovecoding" β "i love coding"
*
* 2. URL PARSING:
* - Split domain-like strings: "stackoverflow" β "stack overflow"
* - Parse hashtags: "#machinelearning" β "machine learning"
*
* 3. KEYBOARD AUTOCOMPLETE:
* - Segment user input without spaces
* - Suggest word boundaries
*
* 4. LANGUAGE PROCESSING:
* - Chinese/Japanese text (no spaces between words)
* - Named entity recognition
*
* ============================================================================
* VISUALIZATION - Step by Step
* ============================================================================
*
* s = "leetcode", wordDict = ["leet", "code"]
*
* dp[i] = true if s[0..i-1] can be segmented into dictionary words
*
* String: l e e t c o d e
* Index: 0 1 2 3 4 5 6 7
* dp index: 1 2 3 4 5 6 7 8
*
* βββββββββ¬ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
* β i β Check β
* βββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
* β dp[0] β true (empty string is valid base case) β
* βββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
* β dp[1] β s[0:1]="l" in dict? No β false β
* β dp[2] β s[0:2]="le" in dict? No β false β
* β dp[3] β s[0:3]="lee" in dict? No β false β
* βββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
* β dp[4] β s[0:4]="leet" in dict? YES! And dp[0]=true β
* β β dp[4] = true β¨ β
* βββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
* β dp[5] β s[0:5]="leetc"? No, s[4:5]="c"? No β false β
* β dp[6] β s[0:6]="leetco"? No, s[4:6]="co"? No β false β
* β dp[7] β s[0:7]="leetcod"? No, s[4:7]="cod"? No β false β
* βββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
* β dp[8] β s[4:8]="code" in dict? YES! And dp[4]=true β
* β β dp[8] = true β¨ ANSWER! β
* βββββββββ΄ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
*
* Answer: true ("leet" + "code")
*
* ============================================================================
* π KEY INSIGHT: "Is there a valid split point?"
* ============================================================================
*
* For each position i, check ALL possible split points j:
* - Is s[0..j-1] valid? (dp[j] = true)
* - Is s[j..i-1] in the dictionary?
* - If BOTH yes β dp[i] = true
*
* Recurrence: dp[i] = true if β j where dp[j]=true AND s[j:i] in dict
*
* Visual:
* s = "leetcode"
* ββββββ ββββββ
* leet code
* j=0 j=4
* dp[4]=T in dict
*
* ============================================================================
* COMPLEXITY
* ============================================================================
* Time: O(nΒ² Γ k) - n positions, n split points, k = avg word length for substring
* Space: O(n) - dp array
*/
export function wordBreak(s: string, wordDict: string[]): boolean {
const wordSet = new Set(wordDict); // O(1) lookup
const dp = new Array(s.length + 1).fill(false);
dp[0] = true; // Empty string is valid
for (let i = 1; i <= s.length; i++) {
// Try all possible split points
for (let j = 0; j < i; j++) {
// If s[0..j-1] is valid AND s[j..i-1] is in dictionary
if (dp[j] && wordSet.has(s.substring(j, i))) {
dp[i] = true;
break; // Found one valid segmentation, no need to continue
}
}
}
return dp[s.length];
}
/**
* ============================================================================
* 7. Coin Change (LeetCode 322) - UNBOUNDED KNAPSACK PATTERN
* ============================================================================
*
* Given coin denominations and a target amount, return the MINIMUM number
* of coins needed to make that amount. (Unlimited coins of each type!)
*
* ============================================================================
* πͺ RECIPE APPLIED (the five questions from the file header)
* ============================================================================
*
* Q1 CHOICE "Which coin do I use LAST?" Try each denomination.
* Q2 STATE "How much amount is still left to build" β ONE number β 1D
* dp[amount]. Coins are REUSABLE, so you never need to remember
* which ones you already spent. (Contrast #22, 0/1 knapsack,
* where reuse is banned and the state grows a dimension.)
* Q3 TRANSITION dp[i] = min(dp[i - coin] + 1) over every coin that fits.
* Q4 BASE CASE dp[0] = 0 β zero coins make zero.
* Q5 ORDER i = 1 upward; i - coin is always smaller, so already solved.
* ANSWER dp[amount], or -1 if it is still Infinity (unreachable).
*
* β οΈ WHY GREEDY FAILS β say this out loud in an interview: with coins {1,3,4}
* and amount 6, biggest-first gives 4+1+1 = 3 coins, but 3+3 = 2 is optimal.
* Only trying every coin at every amount finds it. Infinity is used as the
* "no way to build this" sentinel so min() naturally ignores it.
*
* ============================================================================
* π REAL-WORLD SCENARIOS
* ============================================================================
*
* 1. CASH REGISTER / ATM:
* - Dispense minimum bills/coins for withdrawal
* - Optimize change given to customers
*
* 2. CURRENCY EXCHANGE:
* - Minimum transactions to convert currencies
* - Optimal denomination selection
*
* 3. RESOURCE ALLOCATION:
* - Minimum servers to handle workload (server sizes vary)
* - Minimum containers to ship cargo
*
* 4. TIME SCHEDULING:
* - Minimum meeting slots (30min, 1hr, 2hr) to cover duration
* - Minimum number of shifts to cover hours
*
* ============================================================================
* VISUALIZATION - Step by Step
* ============================================================================
*
* coins = [1, 2, 5], amount = 11
*
* dp[i] = minimum coins to make amount i
*
* ββββββββββ¬ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
* β Amount β Calculation β
* ββββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
* β 0 β dp[0] = 0 (base case: 0 coins for amount 0) β
* ββββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
* β 1 β Use coin 1: dp[1-1] + 1 = dp[0] + 1 = 1 β
* β β dp[1] = 1 (one $1 coin) β
* ββββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
* β 2 β Use coin 1: dp[2-1] + 1 = dp[1] + 1 = 2 β
* β β Use coin 2: dp[2-2] + 1 = dp[0] + 1 = 1 β Better! β
* β β dp[2] = 1 (one $2 coin) β
* ββββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
* β 3 β Use coin 1: dp[2] + 1 = 2 β
* β β Use coin 2: dp[1] + 1 = 2 β
* β β dp[3] = 2 ($2 + $1 or $1 + $1 + $1... but 2 is better) β
* ββββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
* β 5 β Use coin 1: dp[4] + 1 = 3 β