Phases 3–9 · Questions 14–58
Saat patterns.
Paintalees sawaal.
Ek hi soch.
Phase 3 se 9 tak — Knapsack, Strings, Stocks, LIS, Partition DP, Squares, aur Advanced. Har phase ka ek core recurrence hai, aur us phase ke saare sawaal usi ke variations hain.
Har phase ki shuruaat me uska core recurrence card diya hai. Wo yaad kar lo, baaki sirf knobs ghumane hain. Neeche Phase 3 wala template — sabse pehle yahi.
// base cases — knob 3
notTake = f(i-1, target)
take = <neutral>
if (arr[i] <= target)
take = f(i-1, target - arr[i])
return COMBINE(take, notTake) // knob 1
// return type — knob 2
Phase 3 · Q14–Q24
DP on Subsequences — Knapsack
Har element pe ek hi sawaal: loon ya na loon? Aur state me ek doosra parameter (target / capacity) track hota hai. Poore Phase 3 me sirf teen cheezein badalti hain — combine operator, return type, aur take pe index i rehta hai ya i-1 ho jaata hai.
Subset Sum equals Target
Coding Ninjas / GFG · poore Phase 3 ki neev
Problem
Array arr[] (positive integers) aur ek target diya hai. Batao kya koi subset exist karta hai jiska sum exactly target ho.
arr = [1, 2, 3, 4], target = 4 → true — kyunki {4} ya {1,3} dono kaam karte hain.
Soch
Ye teesra sawaal-type hai jo Phase 1–2 me nahi aaya tha:
| Sawaal | Combine | Kahan dekha |
|---|---|---|
| kitne tareeke (count) | + | Q2, Q8, Q9 |
| minimum / maximum | min / max | Q3, Q5, Q10–Q13 |
| kya possible hai? | || | ab |
Take / Not-Take, ab do parameter ke saath
f(i, target) = "index 0 se i tak ke elements se kya target ban sakta hai?"
- Take —
arr[i]le lo, bacha target =target - arr[i]. Sirf tab jabarr[i] <= target, warna target negative ho jayega. - Not Take — chhod do, target waisa ka waisa.
Kisi ek se true mil gaya toh kaam ho gaya → || lagao.
Java me || short-circuit karta hai. notTake ne true de diya toh take calculate hoga hi nahi.
Base cases
if (target == 0) return true;— kuch aur lene ki zaroorat nahi. Empty selection bhi valid hai.if (i == 0) return arr[0] == target;— sirf ek element bacha.
target == 0 ka check i == 0 se pehle aana chahiye. Ulta likha aur arr = [5], target = 0 aaya, toh arr[0] == 0 → false milega. Galat — empty subset ka sum 0 hota hai.
Code
static boolean f(int i, int target, int[] arr) {
// ✅ target ban gaya
if (target == 0) return true;
// pehla element
if (i == 0) return arr[0] == target;
boolean notTake = f(i - 1, target, arr);
boolean take = false;
if (arr[i] <= target) { // tabhi jab fit ho
take = f(i - 1, target - arr[i], arr);
}
return take || notTake;
}
// call: f(n - 1, target, arr)
static boolean f(int i, int target, int[] arr, int[][] dp) {
if (target == 0) return true;
if (i == 0) return arr[0] == target;
if (dp[i][target] != -1) return dp[i][target] == 1;
boolean notTake = f(i - 1, target, arr, dp);
boolean take = false;
if (arr[i] <= target) {
take = f(i - 1, target - arr[i], arr, dp);
}
boolean ans = take || notTake;
dp[i][target] = ans ? 1 : 0;
return ans;
}
// driver
int[][] dp = new int[n][target + 1];
for (int[] row : dp) Arrays.fill(row, -1);
System.out.println(f(n - 1, target, arr, dp));
boolean array me -1 nahi rakh sakte. Toh int array use karo: -1 = compute nahi hua, 0 = false, 1 = true.
Ye O(n × target) hai — O(n²) nahi. Target bada hua (10⁹) toh ye DP kaam nahi karega. Isko pseudo-polynomial kehte hain. Subset Sum NP-complete hai; ye DP sirf isliye chalta hai kyunki target chhota hota hai.
static boolean f(int n, int target, int[] arr) {
boolean[][] dp = new boolean[n][target + 1];
// base 1: target 0 hamesha possible (kuch mat lo)
for (int i = 0; i < n; i++) dp[i][0] = true;
// base 2: sirf arr[0] available
if (arr[0] <= target) dp[0][arr[0]] = true;
for (int i = 1; i < n; i++) {
for (int t = 1; t <= target; t++) {
boolean notTake = dp[i - 1][t];
boolean take = false;
if (arr[i] <= t) take = dp[i - 1][t - arr[i]];
dp[i][t] = take || notTake;
}
}
return dp[n - 1][target];
}
static boolean f(int n, int target, int[] arr) {
boolean[] prev = new boolean[target + 1];
prev[0] = true;
if (arr[0] <= target) prev[arr[0]] = true;
for (int i = 1; i < n; i++) {
boolean[] curr = new boolean[target + 1];
curr[0] = true; // ⚠️ har row me!
for (int t = 1; t <= target; t++) {
boolean notTake = prev[t];
boolean take = false;
if (arr[i] <= t) take = prev[t - arr[i]];
curr[t] = take || notTake;
}
prev = curr;
}
return prev[target];
}
curr[0] = true bhoolna. Har naya curr Java me false se initialize hota hai — set nahi kiya toh agli iteration me prev[0] false hoga aur poora answer galat.
Dry Run
arr = [1, 2, 3, 4], target = 4
| i \ t | 0 | 1 | 2 | 3 | 4 |
|---|---|---|---|---|---|
| 0 arr=1 | T | T | F | F | F |
| 1 arr=2 | T | T | T | T | F |
| 2 arr=3 | T | T | T | T | T |
| 3 arr=4 | T | T | T | T | T |
Row 2 pe kya hua (yahi turning point hai)
| t | notTake = dp[1][t] | take (3 ≤ t?) | dp[2][t] |
|---|---|---|---|
| 1 | T | 3 > 1 ❌ | T |
| 2 | T | 3 > 2 ❌ | T |
| 3 | T | dp[1][0] = T | T |
| 4 | F | dp[1][1] = T ✅ | T |
t = 4 pe true take se aaya: arr[2] = 3 liya, aur dp[1][1] = true (matlab {1} se 1 ban gaya). Toh {1, 3} = 4.
Table ko padho
- Column 0 poora T — target 0 hamesha possible.
- Row 0 me sirf
t=0,1T — sirf{1}available tha. - T kabhi F nahi banta. Neeche jaate hue sirf T badhta hai, kyunki
notTakehamesha pichli row copy karta hai. Ye monotonicity boolean DP ki pehchaan hai.
Khud try karo
arr = [5], target = 0→trueaana chahiye. Tumhara code deta hai?- Actual subset print karo (
{1,3}), sirftruenahi. Backtrack:dp[i-1][t]true hai toh element nahi liya (i--), warna liya (t -= arr[i]; i--). arrme0ho toh kya hoga?arr = [0,1], target = 1. Abhi bas soch ke rakho — Q17 me ye bada issue banega.
Partition Equal Subset Sum
LeetCode 416 · reduction ka pehla example
Problem
Array ko do subsets me baanto jinka sum barabar ho. Possible hai ya nahi?
nums = [1, 5, 11, 5] → true: {1,5,5} = 11 aur {11} = 11.
Soch
Naya DP nahi hai. Do observations se poora sawaal Q14 ban jaata hai.
Observation 1 — odd sum = turant false
Total sum S ko do barabar hisson me baantna hai, toh har hissa S/2. Aur S/2 integer tabhi jab S even ho.
if (totalSum % 2 != 0) return false; // ek line me khatam
[1,2,3,5] ka sum 11 (odd) → seedha false, DP chalane ki zaroorat hi nahi.
Observation 2 — ek subset kaafi hai
Agar ek subset mil gaya jiska sum S/2 hai, toh bache hue elements apne aap doosra subset ban jaate hain — aur unka sum bhi automatically S - S/2 = S/2. Dono track karne ki zaroorat hi nahi.
Toh sawaal ban gaya: "kya koi subset hai jiska sum S/2 hai?" — ye toh bilkul Q14 hai.
Code
static boolean canPartition(int[] nums) {
int totalSum = 0;
for (int x : nums) totalSum += x;
if (totalSum % 2 != 0) return false; // Observation 1
return subsetSum(nums, totalSum / 2); // Observation 2
}
static boolean canPartition(int[] nums) {
int totalSum = 0;
for (int x : nums) totalSum += x;
if (totalSum % 2 != 0) return false;
int target = totalSum / 2;
boolean[] dp = new boolean[target + 1];
dp[0] = true;
for (int num : nums) {
for (int t = target; t >= num; t--) { // ⚠️ ULTA loop
dp[t] = dp[t] || dp[t - num];
}
}
return dp[target];
}
Seedha (t = num se target) chalate toh dp[t - num] isi iteration me already update ho chuka hota — matlab num ko do baar use kar liya. 0/1 knapsack me har item ek hi baar.
Ulta chalane se dp[t - num] abhi bhi pichli iteration ki value rakhta hai. ✅
Aur yaad rakho: Q22 (Coin Change II) me hum jaan-boojhke seedha loop chalayenge — kyunki wahan item baar-baar lena allowed hai. Same code, ek loop ki direction, poora meaning badal jaata hai.
Dry Run
nums = [1, 5, 11, 5] → sum 22 (even) → target = 11
| i \ t | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 (1) | T | T | F | F | F | F | F | F | F | F | F | F |
| 1 (5) | T | T | F | F | F | T | T | F | F | F | F | F |
| 2 (11) | T | T | F | F | F | T | T | F | F | F | F | T |
| 3 (5) | T | T | F | F | F | T | T | F | F | F | T | T |
Row 1 padho: {1}=1, {5}=5, {1,5}=6 — bilkul sahi.
Row 2 pe t=11 true ho gaya, {11} se.
Row 3 pe dp[3][11] do raston se true hai:
- notTake:
dp[2][11]= T →{11}, baaki{1,5,5}= 11 ✅ - take:
dp[2][6]= T →nums[3]=5+{1,5}=6 →{1,5,5}✅
Pattern Tag
Naya sawaal → known pattern me convert karo. Interview me 60% questions "disguised" hote hain.
| Sawaal | Kis pattern me convert hua |
|---|---|
| Q6 House Robber II | 2 × House Robber I |
| Q15 Equal Partition | Subset Sum, target = S/2 |
| Delete and Earn | House Robber, frequency array pe |
| Cherry Pickup I | Cherry Pickup II (jaana+aana = 2 robots) |
Khud try karo
[1, 2, 5]→ sum 8 (even!), target 4. Par1+2=3,5,1+5=6… 4 banta hi nahi →false. Even sum se partition guarantee nahi hota.- Ulta loop wale version me seedha loop chala ke dekho.
nums = [1,2], target = 2pe test karo — answer galat aayega? - Dono subsets print karo:
{1,5,5}aur{11}.
Minimum Subset Sum Difference
GFG · "Partition a set into two subsets such that difference of sums is minimum"
Problem
Array ko do subsets me baanto. Dono ke sums ka absolute difference minimum karo. Wo minimum difference return karo.
arr = [1, 2, 3, 4] → 0 ({1,4} aur {2,3}, dono 5)
arr = [1, 6, 11, 5] → 1 ({1,5,5}… nahi — {1,6,5} = 12 aur {11} = 11, diff 1)
Soch
Q15 tak hum sirf ek cell (dp[n-1][target]) dekh rahe the. Yahan pehli baar poori last row kaam aayegi.
Do subsets ko ek variable me kaise laayein
Maan lo total sum S hai, aur ek subset ka sum s1. Toh doosre ka sum apne aap S - s1 hai.
difference = |s1 - (S - s1)| = |S - 2*s1|
Toh mujhe bas ek variable s1 ke saare possible values chahiye, aur har ek pe ye formula chala ke minimum le lena hai.
Aur wo "saare possible values" kahan se aayenge?
Q14 ki dp table ki last row me har cell dp[n-1][s] batata hai ki sum s achievable hai ya nahi. Toh last row ko s = 0 se S tak scan karo, jahan-jahan true mile, wahan |S - 2*s| nikaalo, sabka minimum lo.
Ek subset-sum DP chalao target = S ke saath (poora sum, S/2 nahi — kyunki ab hum sab kuch achievable dekhna chahte hain), phir ek linear scan. Bas.
Sirf s = 0 se S/2 tak scan karna kaafi hai. Kyunki har partition do baar count hota hai (s1 aur S-s1 dono achievable honge), aur half tak scan karne se saare unique partitions cover ho jaate hain.
Code
static int minSubsetSumDifference(int[] arr, int n) {
int totalSum = 0;
for (int x : arr) totalSum += x;
// ---- Q14 ka subset-sum, target = totalSum ----
boolean[][] dp = new boolean[n][totalSum + 1];
for (int i = 0; i < n; i++) dp[i][0] = true;
if (arr[0] <= totalSum) dp[0][arr[0]] = true;
for (int i = 1; i < n; i++) {
for (int t = 1; t <= totalSum; t++) {
boolean notTake = dp[i - 1][t];
boolean take = false;
if (arr[i] <= t) take = dp[i - 1][t - arr[i]];
dp[i][t] = take || notTake;
}
}
// ---- last row scan ----
int mini = Integer.MAX_VALUE;
for (int s1 = 0; s1 <= totalSum / 2; s1++) {
if (dp[n - 1][s1]) {
int s2 = totalSum - s1;
mini = Math.min(mini, Math.abs(s2 - s1));
}
}
return mini;
}
static int minSubsetSumDifference(int[] arr, int n) {
int totalSum = 0;
for (int x : arr) totalSum += x;
boolean[] dp = new boolean[totalSum + 1];
dp[0] = true;
for (int num : arr) {
for (int t = totalSum; t >= num; t--) { // ulta = 0/1
dp[t] = dp[t] || dp[t - num];
}
}
int mini = Integer.MAX_VALUE;
for (int s1 = 0; s1 <= totalSum / 2; s1++) {
if (dp[s1]) mini = Math.min(mini, totalSum - 2 * s1);
}
return mini;
}
s1 <= totalSum/2 hone ki wajah se totalSum - 2*s1 hamesha ≥ 0 hai. Toh Math.abs() ki zaroorat hi nahi.
Dry Run
arr = [1, 6, 11, 5], totalSum = 23
Step 1 — achievable sums (last row)
Subset sums jo ban sakte hain: 0, 1, 5, 6, 7, 11, 12, 16, 17, 18, 22, 23
| s | 0 | 1 | 5 | 6 | 7 | 11 | 12 | 16 | 17 | 18 | 22 | 23 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| dp | T | T | T | T | T | T | T | T | T | T | T | T |
| subset | {} | {1} | {5} | {6} | {1,6} | {11} | {1,11} | {11,5} | {1,11,5} | {1,6,11} | {6,11,5} | all |
Step 2 — scan s1 = 0 to 11
| s1 | achievable? | s2 = 23 - s1 | diff = s2 - s1 |
|---|---|---|---|
| 0 | T | 23 | 23 |
| 1 | T | 22 | 21 |
| 5 | T | 18 | 13 |
| 6 | T | 17 | 11 |
| 7 | T | 16 | 9 |
| 11 | T | 12 | 1 ✅ |
Answer = 1 — partition: {11} = 11 aur {1, 6, 5} = 12.
Table ko padho
Scan me s1 jitna S/2 = 11.5 ke paas jaata hai, diff utna chhota hota jaata hai. Sabse paas wala achievable value 11 tha, isliye wahi jeeta. Agar arr = [1,2,3,4] hota (sum 10), toh s1 = 5 achievable hai → diff 0.
Complexity
| Approach | Time | Space |
|---|---|---|
| Tabulation | O(n × S) | O(n × S) |
| Space Optimized | O(n × S) | O(S) |
Scan sirf O(S) hai — dominate nahi karta.
Khud try karo
[1,2,3,4]→ answer0. Verify karo kis1 = 5achievable hai.- Dono subsets print karo, sirf diff nahi. Q14 wala backtracking, best
s1se shuru karke. - Agar array me negative numbers hon toh? Ye DP kyun toot jaata hai? (Hint:
dpke indices non-negative hote hain.)
Count Subsets with Sum K
Coding Ninjas / GFG · zeros wala classic trap
Problem
Array arr[] aur target k. Batao kitne subsets ka sum exactly k hai.
arr = [1, 2, 2, 3], k = 3 → 3
Kaunse? {1,2} (pehla 2), {1,2} (doosra 2), {3}. Dono {1,2} alag count hote hain kyunki index alag hain, value same hone se farak nahi padta.
Soch
Q14 ka bilkul same recursion. Sirf ek knob ghooma:
| Q14 Subset Sum | Q17 Count Subsets | |
|---|---|---|
| Sawaal | possible hai? | kitne tareeke? |
| Combine | || | + |
| Return | boolean | int |
| Success pe | true | 1 |
| Failure pe | false | 0 |
Yaad karo Q8 (Unique Paths) me bhi yahi tha: return 1 = ek valid tareeka mila, return 0 = raasta bekaar. Wahi ab yahan.
Zeros ka trap — ye sabse important hai
Ab tak humne base case aise likha tha:
// ye zeros ke saath GALAT hai
if (target == 0) return 1;
if (i == 0) return arr[0] == target ? 1 : 0;
arr = [0, 0, 1], k = 1. Sahi answer 4 hai:
{1}, {0₁, 1}, {0₂, 1}, {0₁, 0₂, 1} — har zero ko lena ya na lena, dono valid choices hain, aur sum nahi badalta.
Par upar wala code target == 0 hote hi 1 return kar deta hai aur baaki zeros ki choices explore hi nahi karta. Answer 1 aayega. ❌
Sahi base case
target == 0 wala early-return hata do, aur poora kaam i == 0 pe karo:
if (i == 0) {
if (target == 0 && arr[0] == 0) return 2; // take AND notTake, dono se 0
if (target == 0 || arr[0] == target) return 1;
return 0;
}
Teen line ka matlab:
return 2— target 0 chahiye aurarr[0]bhi 0 hai. Toh{}aur{0}— do alag subsets, dono ka sum 0.return 1— ya toh target 0 hai (empty subset lo) yaarr[0]exactly target hai. Ek tareeka.return 0— kuch nahi ban sakta.
Counting problems me early return se bacho. Recursion ko poora neeche tak jaane do, taaki har element ki take/notTake choice count ho. Boolean problems me early return safe tha (Q14) kyunki wahan true ek hi baar count hota hai.
Code
static int f(int i, int target, int[] arr, int[][] dp) {
if (i == 0) {
if (target == 0 && arr[0] == 0) return 2;
if (target == 0 || arr[0] == target) return 1;
return 0;
}
if (dp[i][target] != -1) return dp[i][target];
int notTake = f(i - 1, target, arr, dp);
int take = 0;
if (arr[i] <= target) {
take = f(i - 1, target - arr[i], arr, dp);
}
return dp[i][target] = take + notTake; // COMBINE = +
}
// driver
int[][] dp = new int[n][k + 1];
for (int[] row : dp) Arrays.fill(row, -1);
System.out.println(f(n - 1, k, arr, dp));
static int f(int n, int k, int[] arr) {
int[][] dp = new int[n][k + 1];
// base: i == 0
if (arr[0] == 0) dp[0][0] = 2; // {} aur {0}
else dp[0][0] = 1; // sirf {}
if (arr[0] != 0 && arr[0] <= k) dp[0][arr[0]] = 1;
for (int i = 1; i < n; i++) {
for (int t = 0; t <= k; t++) {
int notTake = dp[i - 1][t];
int take = 0;
if (arr[i] <= t) take = dp[i - 1][t - arr[i]];
dp[i][t] = take + notTake;
}
}
return dp[n - 1][k];
}
1. t ka loop 0 se shuru hai, 1 se nahi. Q14 me 1 se chal jaata tha kyunki column 0 pehle hi bhar diya tha. Yahan zeros ki wajah se dp[i][0] bhi compute hona chahiye.
2. arr[0] != 0 ka check zaroori hai, warna arr[0] = 0 hone pe dp[0][0] ki 2 wali value 1 se overwrite ho jayegi.
static int f(int n, int k, int[] arr) {
int[] prev = new int[k + 1];
if (arr[0] == 0) prev[0] = 2; else prev[0] = 1;
if (arr[0] != 0 && arr[0] <= k) prev[arr[0]] = 1;
for (int i = 1; i < n; i++) {
int[] curr = new int[k + 1];
for (int t = 0; t <= k; t++) {
int notTake = prev[t];
int take = 0;
if (arr[i] <= t) take = prev[t - arr[i]];
curr[t] = take + notTake;
}
prev = curr;
}
return prev[k];
}
Dry Run
arr = [1, 2, 2, 3], k = 3
Base: arr[0] = 1 (non-zero) → dp[0][0] = 1, dp[0][1] = 1
| i \ t | 0 | 1 | 2 | 3 |
|---|---|---|---|---|
| 0 arr=1 | 1 | 1 | 0 | 0 |
| 1 arr=2 | 1 | 1 | 1 | 1 |
| 2 arr=2 | 1 | 1 | 2 | 2 |
| 3 arr=3 | 1 | 1 | 2 | 3 |
Cell by cell — row 2 (doosra 2)
| t | notTake = dp[1][t] | take (2 ≤ t?) | dp[2][t] |
|---|---|---|---|
| 0 | 1 | ❌ | 1 |
| 1 | 1 | ❌ | 1 |
| 2 | 1 | dp[1][0] = 1 | 2 |
| 3 | 1 | dp[1][1] = 1 | 2 |
Final cell — dp[3][3]
- notTake =
dp[2][3]= 2 →{1,2₁}aur{1,2₂} - take =
dp[2][0]= 1 →{3} - Total = 3 ✅
Table ko padho
Column t=2 dekho: row 1 pe 1, row 2 pe 2. Jaise hi doosra 2 array me aaya, sum 2 banane ke tareeke double ho gaye. Yahi + operator ka kaam hai — || hota toh dono baar true hi rehta aur farak dikhta hi nahi.
Khud try karo
arr = [0, 0, 1],k = 1→ answer 4. Purana (galat) base case laga ke dekho — kya1aata hai?arr = [0, 0, 0],k = 0→ answer 8 (har zero ki 2 choices, 2³). Verify karo.- Bade arrays me count overflow ho sakta hai. LeetCode aksar
mod 1e9+7maangta hai — code me kahan-kahan% MODlagega?
Count Partitions with Given Difference
Coding Ninjas / GFG · algebra + Q17
Problem
Array ko do subsets S1 aur S2 me baanto (har element exactly ek subset me) taaki sum(S1) - sum(S2) = D. Kitne tareeke hain?
arr = [5, 2, 6, 4], D = 3 → 1 — sirf ek partition kaam karta hai: S1 = {6,4} = 10 aur S2 = {5,2} = 7, diff 10 − 7 = 3 ✅
Soch — do equations, do unknowns
Pehle algebra, phir Q17. Diye hue hain:
S1 + S2 = totalSum // dono milke poora array
S1 - S2 = D // diya hua condition
Doosre ko pehle se ghatao:
2 * S2 = totalSum - D
S2 = (totalSum - D) / 2
Ab S2 ek fixed number hai. Toh sawaal ban gaya: "kitne subsets ka sum (totalSum - D)/2 hai?" — ye Q17 hai.
Aur dobara wahi observation jo Q15 me tha: ek subset chun liya, toh doosra apne aap ban gaya. Sirf ek count karo.
Do edge cases — dono zaroori
(totalSum - D) < 0→ return 0. Negative sum wala subset exist nahi karta.(totalSum - D) % 2 != 0→ return 0.S2integer hi nahi banega.
Ye do checks bina DP chalaye answer de dete hain. Q15 ke totalSum % 2 != 0 wale check ka hi bada version hai.
Code
static final int MOD = 1000000007;
static int countPartitions(int n, int D, int[] arr) {
int totalSum = 0;
for (int x : arr) totalSum += x;
// edge cases
if (totalSum - D < 0) return 0;
if ((totalSum - D) % 2 != 0) return 0;
int target = (totalSum - D) / 2;
return countSubsets(n, target, arr); // Q17 ka function
}
static int countSubsets(int n, int k, int[] arr) {
int[] prev = new int[k + 1];
if (arr[0] == 0) prev[0] = 2; else prev[0] = 1;
if (arr[0] != 0 && arr[0] <= k) prev[arr[0]] = 1;
for (int i = 1; i < n; i++) {
int[] curr = new int[k + 1];
for (int t = 0; t <= k; t++) {
int notTake = prev[t];
int take = 0;
if (arr[i] <= t) take = prev[t - arr[i]];
curr[t] = (take + notTake) % MOD;
}
prev = curr;
}
return prev[k];
}
Q17 ka zero-handling base case yahan bina badle use karna hai. Is problem me arrays me zeros aksar hote hain aur test cases specifically unko target karte hain.
Dry Run
arr = [5, 2, 6, 4], D = 3
totalSum = 5+2+6+4 = 17
17 - 3 = 14, even ✅ → target = 7
Ab: kitne subsets ka sum 7 hai?
| i \ t | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|---|---|---|---|---|---|---|---|---|
| 0 (5) | 1 | 0 | 0 | 0 | 0 | 1 | 0 | 0 |
| 1 (2) | 1 | 0 | 1 | 0 | 0 | 1 | 0 | 1 |
| 2 (6) | 1 | 0 | 1 | 0 | 0 | 1 | 1 | 1 |
| 3 (4) | 1 | 0 | 1 | 0 | 1 | 1 | 1 | 1 |
Row 1 pe kya hua
dp[1][7]: notTake = dp[0][7] = 0, take = dp[0][5] = 1 → total 1. Matlab {5, 2} = 7 ✅
Final answer
dp[3][7] = 1 → 1 tareeka
Verify: S2 = {5,2} = 7, toh S1 = {6,4} = 10. Diff = 10 - 7 = 3 ✅
Complexity
| Approach | Time | Space |
|---|---|---|
| Memoization | O(n × target) | O(n × target) + O(n) |
| Tabulation | O(n × target) | O(n × target) |
| Space Optimized | O(n × target) | O(target) |
Khud try karo
D = 17daalo (= totalSum). Answer? (target = 0→ sirf empty subset → 1.)D = 18daalo.17 - 18 = -1 < 0→ 0. Edge case ne bacha liya.D = 4daalo.17 - 4 = 13, odd → 0. Verify karo ki koi partition sach me diff 4 nahi de sakta. (Hint:S1 - S2aurS1 + S2ki parity hamesha same hoti hai.)- Q21 (Target Sum) pehle khud try karo — wo bilkul yahi sawaal hai, sirf shabd badle hue.
0/1 Knapsack
Coding Ninjas / GFG · poore Phase 3 ka naam isi pe hai
Problem
Ek chor ke paas W capacity ka bag hai. n items hain, har item ka wt[i] aur val[i]. Har item ya toh poora lo, ya bilkul mat lo (isliye "0/1"). Maximum value nikalo.
wt = [1, 2, 4, 5], val = [5, 4, 8, 6], W = 5 → 13
Kaise? Item 0 (wt 1, val 5) + item 2 (wt 4, val 8) = weight 5, value 13 ✅
Soch
Ab tak target "banana tha". Ab W "bachana hai". Structure bilkul same — bas naam badla.
f(i, W) = "item 0 se i tak, capacity W me maximum kitni value bhar sakta hoon"
- Take —
val[i] + f(i-1, W - wt[i]), sirf jabwt[i] <= W - Not Take —
0 + f(i-1, W)
Maximum chahiye → Math.max()
Q14–Q18 me "take" karne pe sirf target ghatta tha, kuch milta nahi tha. Yahan take karne pe val[i] milta bhi hai. Ye Q5 (House Robber) jaisa hai — wahan bhi nums[i] milta tha. Bas ab ek doosra parameter (W) bhi track ho raha hai.
Base case
if (i == 0) {
if (wt[0] <= W) return val[0]; // fit hota hai, le lo
return 0; // nahi fit, kuch nahi milega
}
Ek line me: return (wt[0] <= W) ? val[0] : 0;
Kyunki base case khud handle kar leta hai: W = 0 hone pe wt[0] <= 0 false hoga (weights positive hain), toh 0 return ho jayega. Q14 me target == 0 ka alag check chahiye tha kyunki wahan wo success tha; yahan wo bas "kuch nahi mila" hai.
Code
static int f(int i, int W, int[] wt, int[] val) {
if (i == 0) return (wt[0] <= W) ? val[0] : 0;
int notTake = 0 + f(i - 1, W, wt, val);
int take = Integer.MIN_VALUE;
if (wt[i] <= W) {
take = val[i] + f(i - 1, W - wt[i], wt, val);
}
return Math.max(take, notTake);
}
// call: f(n - 1, W, wt, val)
take ko Integer.MIN_VALUE se initialize kiya hai, 0 se nahi. Kyunki 0 ek valid answer ho sakta hai, aur hum chahte hain ki "item fit hi nahi hua" wala case max me kabhi na jeete. Q10 wala hi rule, ulta: min ke liye 1e9, max ke liye MIN_VALUE. (Yahan values non-negative hain toh 0 bhi chal jaata, par aadat sahi rakho.)
static int f(int i, int W, int[] wt, int[] val, int[][] dp) {
if (i == 0) return (wt[0] <= W) ? val[0] : 0;
if (dp[i][W] != -1) return dp[i][W];
int notTake = 0 + f(i - 1, W, wt, val, dp);
int take = Integer.MIN_VALUE;
if (wt[i] <= W) {
take = val[i] + f(i - 1, W - wt[i], wt, val, dp);
}
return dp[i][W] = Math.max(take, notTake);
}
// driver
int[][] dp = new int[n][W + 1];
for (int[] row : dp) Arrays.fill(row, -1);
System.out.println(f(n - 1, W, wt, val, dp));
static int f(int n, int W, int[] wt, int[] val) {
int[][] dp = new int[n][W + 1];
// base: sirf item 0 available
for (int c = wt[0]; c <= W; c++) {
dp[0][c] = val[0];
}
for (int i = 1; i < n; i++) {
for (int c = 0; c <= W; c++) {
int notTake = 0 + dp[i - 1][c];
int take = Integer.MIN_VALUE;
if (wt[i] <= c) take = val[i] + dp[i - 1][c - wt[i]];
dp[i][c] = Math.max(take, notTake);
}
}
return dp[n - 1][W];
}
Base case ka loop wt[0] se shuru hota hai — usse chhoti capacity me item 0 fit hi nahi hota, aur Java me wo cells default 0 hi rehte hain. ✅
static int f(int n, int W, int[] wt, int[] val) {
int[] dp = new int[W + 1];
for (int c = wt[0]; c <= W; c++) dp[c] = val[0];
for (int i = 1; i < n; i++) {
for (int c = W; c >= 0; c--) { // ⚠️ ULTA loop
int notTake = dp[c]; // abhi bhi purani row
int take = Integer.MIN_VALUE;
if (wt[i] <= c) take = val[i] + dp[c - wt[i]];
dp[c] = Math.max(take, notTake);
}
}
return dp[W];
}
dp[c - wt[i]] humesha chhota index hai. Ulta chalne se wo abhi tak update nahi hua — matlab pichli row ki value hai, jo humein chahiye. Seedha chalate toh item i do baar use ho jaata (aur wo Q23 Unbounded Knapsack ban jaata — jo actually ek alag sawaal hai, galti nahi 😄).
Dry Run
wt = [1, 2, 4, 5], val = [5, 4, 8, 6], W = 5
Base row: wt[0] = 1, toh c = 1 se aage sab 5.
| i \ c | 0 | 1 | 2 | 3 | 4 | 5 |
|---|---|---|---|---|---|---|
| 0 wt1 v5 | 0 | 5 | 5 | 5 | 5 | 5 |
| 1 wt2 v4 | 0 | 5 | 5 | 9 | 9 | 9 |
| 2 wt4 v8 | 0 | 5 | 5 | 9 | 9 | 13 |
| 3 wt5 v6 | 0 | 5 | 5 | 9 | 9 | 13 |
Row 1 — item (wt 2, val 4)
| c | notTake = dp[0][c] | take (2 ≤ c?) | dp[1][c] |
|---|---|---|---|
| 2 | 5 | 4 + dp[0][0] = 4 | 5 |
| 3 | 5 | 4 + dp[0][1] = 9 ✅ | 9 |
| 5 | 5 | 4 + dp[0][3] = 9 ✅ | 9 |
c = 2 pe interesting hai: item 1 lene se sirf 4 milta, par item 0 akela 5 deta hai. notTake jeeta.
Row 2 — item (wt 4, val 8) — yahan answer banta hai
dp[2][5]:
- notTake =
dp[1][5]= 9 - take =
8 + dp[1][1]=8 + 5= 13 ✅ max(13, 9)= 13
Matlab item 2 (wt 4) liya, aur bachi hui 1 capacity me item 0 (wt 1, val 5) fit ho gaya. {item 0, item 2} → weight 5, value 13 ✅
Row 3 — item (wt 5, val 6)
dp[3][5]: notTake = 13, take = 6 + dp[2][0] = 6. max(13, 6) = 13. Bhaari item liya hi nahi — poori bag khaa jaata aur sirf 6 deta.
Table ko padho
Har row ka matlab: "item 0..i available hain". Neeche jaate hue values kabhi ghatti nahi — kyunki naya item milne se options sirf badhte hain, ghatte nahi. Ye monotonicity knapsack tables ki pehchaan hai (Q14 ki boolean monotonicity ka numeric version).
Complexity
| Approach | Time | Space |
|---|---|---|
| Recursion | O(2ⁿ) | O(n) stack |
| Memoization | O(n × W) | O(n × W) + O(n) |
| Tabulation | O(n × W) | O(n × W) |
| Space Optimized | O(n × W) | O(W) |
Khud try karo
- Kaunse items liye, wo print karo. Backtrack:
dp[i][c] == dp[i-1][c]hai toh iteminahi liya; warna liya (c -= wt[i]). - Single-array version me seedha loop chala ke dekho.
wt=[1], val=[5], W=3pe kya aata hai? (15— item teen baar le liya!) Yahi Q23 ban jaata hai. - Fractional Knapsack (GFG) — item ka tukda bhi le sakte ho. Ye DP nahi hai, greedy hai (value/weight ratio se sort). Socho ki 0/1 me greedy kyun fail hota hai.
Minimum Coins
LeetCode 322 · Coin Change I · pehla unbounded sawaal
Problem
Coins ke denominations coins[] diye hain (infinite supply har coin ka). Amount T banane ke liye minimum kitne coins chahiye? Na ban sake toh -1.
coins = [1, 2, 5], T = 11 → 3 (5 + 5 + 1)
Soch — Unbounded ka janm
Ab tak har item ek hi baar le sakte the, isliye take karne pe f(i-1, ...) jaate the — "is item se kaam khatam, aage badho".
Yahan coin baar-baar le sakte ho. Toh take karne pe index wahi rehta hai:
notTake = 0 + f(i - 1, T) // is coin ko chhod diya, ab kabhi nahi
take = 1 + f(i, T - coins[i]) // ⚠️ i, i-1 NAHI
Take pe i rakho, i-1 nahi. Bas. Q20, Q22, Q23, Q24 — chaaron isi ek badlaav pe khade hain. Baaki sab 0/1 jaisa hi hai.
take me 1 + kyun? Kyunki humein coins ki ginti minimize karni hai. Ek coin uthaya, ginti ek badhi. (Q19 me val[i] + tha kyunki wahan value maximize karni thi.)
Base case — yahan naya twist hai
if (i == 0) {
if (T % coins[0] == 0) return T / coins[0];
return (int) 1e9; // possible hi nahi
}
Sirf ek coin bacha. Agar T us coin se poora divide ho jaata hai, toh T / coins[0] coins lagenge. Warna ye raasta bekaar hai.
Minimization me invalid ko bada banana hota hai (Q10 wali table). 0 return karte toh min usko turant chun leta aur answer 0 aa jaata. Aur Integer.MAX_VALUE mat use karna — uspe 1 + lagte hi overflow ho jayega.
Final answer par ek check
int ans = f(n - 1, T, coins, dp);
return (ans >= (int) 1e9) ? -1 : ans;
Agar answer abhi bhi 1e9 ke aaspaas hai, matlab koi valid combination mila hi nahi.
Code
static int f(int i, int T, int[] coins) {
if (i == 0) {
if (T % coins[0] == 0) return T / coins[0];
return (int) 1e9;
}
int notTake = 0 + f(i - 1, T, coins);
int take = (int) 1e9;
if (coins[i] <= T) {
take = 1 + f(i, T - coins[i], coins); // i same!
}
return Math.min(take, notTake);
}
static int coinChange(int[] coins, int T) {
int n = coins.length;
int[][] dp = new int[n][T + 1];
for (int[] row : dp) Arrays.fill(row, -1);
int ans = f(n - 1, T, coins, dp);
return (ans >= (int) 1e9) ? -1 : ans;
}
static int f(int i, int T, int[] coins, int[][] dp) {
if (i == 0) {
if (T % coins[0] == 0) return T / coins[0];
return (int) 1e9;
}
if (dp[i][T] != -1) return dp[i][T];
int notTake = 0 + f(i - 1, T, coins, dp);
int take = (int) 1e9;
if (coins[i] <= T) take = 1 + f(i, T - coins[i], coins, dp);
return dp[i][T] = Math.min(take, notTake);
}
Recursion tree me depth T / min(coins) tak ja sakti hai (ek hi coin baar-baar), toh dekhne me dar lagta hai. Par states sirf n × T hain, isliye memoization ke baad O(n × T). Har state ek hi baar compute hoti hai, chahe kitni baar bhi call ho.
static int f(int n, int T, int[] coins) {
int[][] dp = new int[n][T + 1];
// base: sirf coins[0]
for (int t = 0; t <= T; t++) {
if (t % coins[0] == 0) dp[0][t] = t / coins[0];
else dp[0][t] = (int) 1e9;
}
for (int i = 1; i < n; i++) {
for (int t = 0; t <= T; t++) {
int notTake = 0 + dp[i - 1][t];
int take = (int) 1e9;
if (coins[i] <= t) take = 1 + dp[i][t - coins[i]]; // dp[i], dp[i-1] nahi
dp[i][t] = Math.min(take, notTake);
}
}
int ans = dp[n - 1][T];
return (ans >= (int) 1e9) ? -1 : ans;
}
take me dp[i][t - coins[i]] hai — usi row ka pichla cell, pichli row ka nahi. Aur kyunki t ka loop seedha (badhta hua) chal raha hai, wo cell isi iteration me already bhar chuka hai — matlab coin dobara use ho sakta hai. Exactly wahi cheez jo Q15/Q19 me hum ulta loop laga ke rok rahe the. 🔁
static int f(int n, int T, int[] coins) {
int[] prev = new int[T + 1];
for (int t = 0; t <= T; t++) {
prev[t] = (t % coins[0] == 0) ? t / coins[0] : (int) 1e9;
}
for (int i = 1; i < n; i++) {
int[] curr = new int[T + 1];
for (int t = 0; t <= T; t++) {
int notTake = prev[t];
int take = (int) 1e9;
if (coins[i] <= t) take = 1 + curr[t - coins[i]]; // curr!
curr[t] = Math.min(take, notTake);
}
prev = curr;
}
int ans = prev[T];
return (ans >= (int) 1e9) ? -1 : ans;
}
take me curr[t - coins[i]] hai, prev[...] nahi. Ye unbounded ki pehchaan hai. 0/1 me hamesha prev se lete the (Q14, Q17, Q19). Ek akshar ka farak, poora sawaal alag.
Dry Run
coins = [1, 2, 5], T = 11
Base row (coins[0] = 1): har t ke liye t/1 = t coins.
| i \ t | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 (1) | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 1 (2) | 0 | 1 | 1 | 2 | 2 | 3 | 3 | 4 | 4 | 5 | 5 | 6 |
| 2 (5) | 0 | 1 | 1 | 2 | 2 | 1 | 2 | 2 | 3 | 3 | 2 | 3 |
Row 1 — coin 2 add hua
| t | notTake = dp[0][t] | take = 1 + dp[1][t-2] | dp[1][t] |
|---|---|---|---|
| 2 | 2 | 1 + dp[1][0] = 1 ✅ | 1 |
| 4 | 4 | 1 + dp[1][2] = 1+1 = 2 ✅ | 2 |
| 5 | 5 | 1 + dp[1][3] = 1+2 = 3 ✅ | 3 |
t = 4 pe dekho: dp[1][2] use hua — jo abhi isi row me bharaa tha. Matlab coin 2 do baar lag gaya (2 + 2 = 4). Unbounded live in action. 🔁
Row 2 — coin 5 add hua — final
dp[2][11]:
- notTake =
dp[1][11]= 6 (2×5 + 1— chhe coins) - take =
1 + dp[2][6]=1 + 2= 3 ✅ min(3, 6)= 3
Aur dp[2][6] = 2 khud 1 + dp[2][1] = 1 + 1 se aaya. Toh poora chain: 5 + 5 + 1 → 3 coins ✅
Table ko padho
Row 2 me t = 5 pe value 3 se girkar 1 ho gayi, aur t = 10 pe 5 se 2. Jahan-jahan naya coin exactly fit hota hai, wahan bada jump aata hai. Row 1 ki values kabhi badhti nahi row 0 se — naya coin milne se options sirf sudhar sakte hain.
Complexity
| Approach | Time | Space |
|---|---|---|
| Memoization | O(n × T) | O(n × T) + O(T) stack |
| Tabulation | O(n × T) | O(n × T) |
| Space Optimized | O(n × T) | O(T) |
Stack depth O(T) hai (na ki O(n)), kyunki ek hi coin baar-baar lene se recursion gehri jaati hai.
Khud try karo
coins = [2], T = 3→-1. Base case1e9return karta hai, aur final check use-1banata hai. Trace karo.- Kaunse coins liye wo print karo:
[5, 5, 1]. - Greedy try karo: hamesha sabse bada coin lo.
coins = [1, 3, 4], T = 6pe greedy4+1+1= 3 coins deta hai, par sahi answer3+3= 2 hai. Isiliye DP chahiye.
Target Sum
LeetCode 494 · Q18 hi hai, naye kapde me
Problem
Array nums[] hai. Har element ke aage + ya - lagana hai. Kitne tareeke hain jisse final sum target ban jaye?
nums = [1, 1, 1, 1, 1], target = 3 → 5
Ek 1 ko minus, baaki chaar ko plus: -1+1+1+1+1 = 3. Kaunsa wala minus hoga — 5 choices. ✅
Soch — ek reframe, aur sawaal khatam
Har element do dabbo me ja raha hai: plus wala dabba aur minus wala dabba. Toh actually ye ek partition hi hai!
S1= plus wale elements ka sumS2= minus wale elements ka sum
Condition: S1 - S2 = target
"Count partitions where sum(S1) - sum(S2) = D" — wahi sawaal, bas D ka naam yahan target hai. Naya code likhna hi nahi hai.
Toh wahi algebra:
S1 + S2 = totalSum
S1 - S2 = target
------------------------
S2 = (totalSum - target) / 2
Aur count karo kitne subsets ka sum S2 hai.
Edge cases — Q18 wale hi
totalSum - target < 0→ 0(totalSum - target) % 2 != 0→ 0
LeetCode 494 me target negative ho sakta hai. Tab totalSum - target aur bada ho jaata hai — aur agar wo totalSum se bada nikla, toh koi subset nahi banega aur DP khud 0 de dega. Par safety ke liye Math.abs(target) > totalSum ka check bhi laga sakte ho.
Code
static int findTargetSumWays(int[] nums, int target) {
int totalSum = 0;
for (int x : nums) totalSum += x;
if (totalSum - target < 0) return 0;
if ((totalSum - target) % 2 != 0) return 0;
int s2 = (totalSum - target) / 2;
return countSubsets(nums.length, s2, nums); // Q17 ka function
}
static int countSubsets(int n, int k, int[] arr) {
int[] prev = new int[k + 1];
if (arr[0] == 0) prev[0] = 2; else prev[0] = 1;
if (arr[0] != 0 && arr[0] <= k) prev[arr[0]] = 1;
for (int i = 1; i < n; i++) {
int[] curr = new int[k + 1];
for (int t = 0; t <= k; t++) {
int notTake = prev[t];
int take = 0;
if (arr[i] <= t) take = prev[t - arr[i]];
curr[t] = take + notTake;
}
prev = curr;
}
return prev[k];
}
LeetCode 494 ke constraints me nums[i] 0 ho sakta hai. Q17 ka zero-handling base case bina badle rakho, warna kai test cases fail honge.
Dry Run
nums = [1, 1, 1, 1, 1], target = 3
totalSum = 5. 5 - 3 = 2, even ✅ → s2 = 1
Ab: kitne subsets ka sum 1 hai? Paanch 1s me se koi ek chuno → 5 tareeke.
| i \ t | 0 | 1 |
|---|---|---|
| 0 | 1 | 1 |
| 1 | 1 | 2 |
| 2 | 1 | 3 |
| 3 | 1 | 4 |
| 4 | 1 | 5 |
Har row me t = 1 ki value ek badh rahi hai: notTake (pichli row) + take (dp[i-1][0] = 1). Naya 1 milne se ek naya tareeka.
Verify karo
s2 = 1 matlab minus wale dabbe me sum 1 — ek akela 1. Baaki chaar plus me.
S1 = 4, S2 = 1 → 4 - 1 = 3 ✅ Bilkul target.
Pattern Tag
Q17, Q18, aur Q21 — teeno ka core countSubsets() ek hi hai. Sirf wrapper ka algebra badalta hai. Interview me agar ye reduction turant dikh jaye, toh 15 minute ka sawaal 3 minute me khatam.
| Sawaal | Kya poochha | Target kya banta hai |
|---|---|---|
| Q17 | subsets with sum k | k (seedha) |
| Q18 | partitions with diff D | (S − D) / 2 |
| Q21 | +/− signs for target | (S − target) / 2 |
Khud try karo
nums = [1], target = 1→ 1. Aurtarget = 2? (1 - 2 = -1 < 0→ 0.)nums = [1,0], target = 1→ answer 2 (+1+0aur+1-0). Zero-handling base case bina ye1dega. Test karo.- Doosri algebra try karo:
S1 = (totalSum + target) / 2nikaal keS1count karo. Same answer aayega? (Haan — symmetric hai.)
Coin Change II
LeetCode 518 · unbounded + counting
Problem
Coins ke denominations diye hain (infinite supply). Amount T banane ke kitne tareeke hain?
coins = [1, 2, 5], T = 5 → 4
5 · 2+2+1 · 2+1+1+1 · 1+1+1+1+1
2+2+1 aur 1+2+2 ek hi tareeka hai — combination, permutation nahi. Aur DP ka structure ye automatically handle karta hai, kyunki hum coins ko index order me process karte hain (coin 0 ke saare decisions, phir coin 1 ke, ...). Ek hi multiset do baar ginne ka mauka hi nahi milta.
Soch
Do knobs ek saath ghoom rahe hain, dono pehle dekh chuke ho:
| Q20 Min Coins | Q22 Coin Change II | |
|---|---|---|
| Sawaal | minimum kitne | kitne tareeke |
| Combine | min | + |
| Take pe | 1 + (coin ginti) | kuch nahi jodo |
| Index | f(i, ...) | f(i, ...) — same |
notTake = f(i - 1, T)
take = f(i, T - coins[i]) // i same (unbounded), aur kuch jodna nahi
return take + notTake // COMBINE = +
Base case
if (i == 0) {
return (T % coins[0] == 0) ? 1 : 0;
}
Sirf ek coin bacha. Agar T us se divide ho jaata hai, toh exactly ek tareeka hai (utne saare coins lo). Warna zero tareeke.
Q20 me yahi base case T / coins[0] return karta tha — kitne coins. Yahan 1 return karta hai — kitne tareeke. Ek hi condition, do alag matlab. Yahi knob 2 (return type) hai.
Code
static int f(int i, int T, int[] coins, long[][] dp) {
if (i == 0) return (T % coins[0] == 0) ? 1 : 0;
if (dp[i][T] != -1) return (int) dp[i][T];
int notTake = f(i - 1, T, coins, dp);
int take = 0;
if (coins[i] <= T) {
take = f(i, T - coins[i], coins, dp); // i same
}
dp[i][T] = take + notTake;
return (int) dp[i][T];
}
// driver
long[][] dp = new long[n][T + 1];
for (long[] row : dp) Arrays.fill(row, -1);
System.out.println(f(n - 1, T, coins, dp));
static long f(int n, int T, int[] coins) {
long[][] dp = new long[n][T + 1];
// base: sirf coins[0]
for (int t = 0; t <= T; t++) {
dp[0][t] = (t % coins[0] == 0) ? 1 : 0;
}
for (int i = 1; i < n; i++) {
for (int t = 0; t <= T; t++) {
long notTake = dp[i - 1][t];
long take = 0;
if (coins[i] <= t) take = dp[i][t - coins[i]]; // dp[i]!
dp[i][t] = take + notTake;
}
}
return dp[n - 1][T];
}
static long f(int n, int T, int[] coins) {
long[] dp = new long[T + 1];
dp[0] = 1; // amount 0: ek tareeka (kuch mat lo)
for (int coin : coins) {
for (int t = coin; t <= T; t++) { // ⚠️ SEEDHA loop
dp[t] += dp[t - coin];
}
}
return dp[T];
}
Q15 me humne ulta loop chalaya tha taaki item dobara use na ho. Yahan seedha chala rahe hain taaki item dobara use ho sake.
dp[t - coin] seedhe loop me isi iteration me already update ho chuka hota hai — matlab wahi coin phir se count ho gaya. Bilkul wahi jo unbounded me chahiye.
0/1 knapsack → ulta loop. Unbounded → seedha loop. Ek line ka farak, do alag problem families.
Dry Run
coins = [1, 2, 5], T = 5
Base row (coins[0] = 1): har amount ek hi tareeke se banega (saare 1s) → sab 1.
| i \ t | 0 | 1 | 2 | 3 | 4 | 5 |
|---|---|---|---|---|---|---|
| 0 (1) | 1 | 1 | 1 | 1 | 1 | 1 |
| 1 (2) | 1 | 1 | 2 | 2 | 3 | 3 |
| 2 (5) | 1 | 1 | 2 | 2 | 3 | 4 |
Row 1 — coin 2 add hua
| t | notTake = dp[0][t] | take = dp[1][t-2] | dp[1][t] | Kya matlab |
|---|---|---|---|---|
| 2 | 1 | dp[1][0] = 1 | 2 | {1,1} · {2} |
| 3 | 1 | dp[1][1] = 1 | 2 | {1,1,1} · {2,1} |
| 4 | 1 | dp[1][2] = 2 | 3 | {1×4} · {2,1,1} · {2,2} |
| 5 | 1 | dp[1][3] = 2 | 3 | {1×5} · {2,1,1,1} · {2,2,1} |
t = 4 pe dp[1][2] use hua — jo isi row me abhi bhara tha aur khud me coin 2 shaamil karta hai. Isliye {2,2} count ho gaya. Unbounded. 🔁
Row 2 — coin 5, final
dp[2][5] = notTake dp[1][5] = 3 + take dp[2][0] = 1 = 4 ✅
Wo teen: {1×5}, {2,1,1,1}, {2,2,1}. Aur chautha: {5}. ✅
Table ko padho
dp[i][0] = 1 har row me — amount 0 banane ka hamesha exactly ek tareeka hai: kuch mat lo. Yahi wo dp[0] = 1 hai jo single-array version me seed karte hain. Aur values kabhi ghatti nahi — naya coin sirf naye tareeke jodta hai.
Complexity
| Approach | Time | Space |
|---|---|---|
| Memoization | O(n × T) | O(n × T) + O(T) stack |
| Tabulation | O(n × T) | O(n × T) |
| Space Optimized | O(n × T) | O(T) |
long use kiya hai kyunki counts tezi se badhte hain aur int overflow ho sakta hai.
Khud try karo
- Single-array version me loops swap karke dekho — bahar
t, andarcoin. Answer 8 aayega (5 ke liye), 4 nahi. Kyunki ab{2,1,2}aur{2,2,1}alag count ho rahe hain — ye permutations gin raha hai. Yahi LeetCode 377 (Combination Sum IV) hai! 🤯 coins = [2], T = 3→ 0. Base case trace karo.- Q15 ka in-place code aur Q22 ka in-place code side-by-side rakho. Sirf loop ki direction aur
||vs+=ka farak hai. Ye do file ek saath dekhna sabse bada "aha" moment hai.
Unbounded Knapsack
Coding Ninjas / GFG · Q19 + unbounded
Problem
Bilkul Q19 wala knapsack, bas har item ka infinite stock hai. Ek item jitni baar chaaho utni baar le sakte ho (jab tak capacity hai).
wt = [2, 4, 6], val = [5, 11, 13], W = 10 → 27
Kaise? Item 1 (wt 4, val 11) do baar + item 0 (wt 2, val 5) ek baar = weight 4+4+2 = 10, value 11+11+5 = 27 ✅
Soch
Q19 se exactly ek akshar ka farak:
// Q19 (0/1)
take = val[i] + f(i - 1, W - wt[i]);
// Q23 (unbounded)
take = val[i] + f(i, W - wt[i]); // i-1 hataya
Bas. Baaki poora code same.
Base case — yahan naya hai
if (i == 0) {
return (W / wt[0]) * val[0];
}
Sirf item 0 bacha, aur uska infinite stock hai. Toh jitne fit ho sakte hain utne bhar do: W / wt[0] copies, har ek val[0] ki.
Q19 me ye (wt[0] <= W) ? val[0] : 0 tha — sirf ek copy. Yahan W/wt[0] copies. Aur agar wt[0] > W ho toh integer division khud 0 de deta hai — alag check ki zaroorat nahi. 👌
Code
static int f(int i, int W, int[] wt, int[] val, int[][] dp) {
if (i == 0) return (W / wt[0]) * val[0];
if (dp[i][W] != -1) return dp[i][W];
int notTake = 0 + f(i - 1, W, wt, val, dp);
int take = Integer.MIN_VALUE;
if (wt[i] <= W) {
take = val[i] + f(i, W - wt[i], wt, val, dp); // i same
}
return dp[i][W] = Math.max(take, notTake);
}
static int f(int n, int W, int[] wt, int[] val) {
int[][] dp = new int[n][W + 1];
for (int c = 0; c <= W; c++) {
dp[0][c] = (c / wt[0]) * val[0];
}
for (int i = 1; i < n; i++) {
for (int c = 0; c <= W; c++) {
int notTake = 0 + dp[i - 1][c];
int take = Integer.MIN_VALUE;
if (wt[i] <= c) take = val[i] + dp[i][c - wt[i]]; // dp[i]
dp[i][c] = Math.max(take, notTake);
}
}
return dp[n - 1][W];
}
static int f(int n, int W, int[] wt, int[] val) {
int[] dp = new int[W + 1];
for (int c = 0; c <= W; c++) dp[c] = (c / wt[0]) * val[0];
for (int i = 1; i < n; i++) {
for (int c = 0; c <= W; c++) { // ⚠️ SEEDHA loop
int notTake = dp[c];
int take = Integer.MIN_VALUE;
if (wt[i] <= c) take = val[i] + dp[c - wt[i]];
dp[c] = Math.max(take, notTake);
}
}
return dp[W];
}
Dono ka single-array code bilkul identical hai, sirf c ka loop ulta/seedha hai. Ye do code ek saath dekhna Phase 3 ka sabse valuable 30 second hai.
Dry Run
wt = [2, 4, 6], val = [5, 11, 13], W = 10
Base row (wt[0]=2, val[0]=5): dp[0][c] = (c/2) * 5
| i \ c | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 wt2 v5 | 0 | 0 | 5 | 5 | 10 | 10 | 15 | 15 | 20 | 20 | 25 |
| 1 wt4 v11 | 0 | 0 | 5 | 5 | 11 | 11 | 16 | 16 | 22 | 22 | 27 |
| 2 wt6 v13 | 0 | 0 | 5 | 5 | 11 | 11 | 16 | 16 | 22 | 22 | 27 |
Row 1 — item (wt 4, val 11)
| c | notTake = dp[0][c] | take = 11 + dp[1][c-4] | dp[1][c] |
|---|---|---|---|
| 4 | 10 | 11 + dp[1][0] = 11 ✅ | 11 |
| 8 | 20 | 11 + dp[1][4] = 11+11 = 22 ✅ | 22 |
| 10 | 25 | 11 + dp[1][6] = 11+16 = 27 ✅ | 27 |
c = 8 pe dp[1][4] use hua — jisme item 1 already ek baar hai. Toh item 1 do baar lag gaya. Yahi unbounded hai. 🔁
c = 10 pe: 11 + dp[1][6], aur dp[1][6] = 16 khud 11 + dp[1][2] = 11 + 5 se aaya. Chain: item1 + item1 + item0 = 11 + 11 + 5 = 27 ✅
Row 2 — item (wt 6, val 13)
dp[2][10]: notTake = 27, take = 13 + dp[2][4] = 13 + 11 = 24. max(27, 24) = 27. Bhaari item liya hi nahi — ratio kharab hai (13/6 ≈ 2.17 vs 11/4 = 2.75).
Ratio dekh ke lagta hai "sabse acha ratio wala item bharte jao". Yahan wo kaam kar gaya, par hamesha nahi karta — kyunki bacha hua space waste ho sakta hai. Example: wt=[5,4], val=[10,7], W=8. Ratio se item 0 (2.0) behtar hai, par {0} = 10 aur {1,1} = 14. DP hi sahi jawaab deta hai.
Khud try karo
- Q19 aur Q23 ka single-array code likh ke sirf loop direction badlo, aur same input pe dono chalao.
wt=[1], val=[5], W=3: 0/1 se 5, unbounded se 15. - Kaunse items kitni baar liye, wo print karo: item1 × 2, item0 × 1.
- Q20 (Min Coins) aur Q23 me kya common hai? (Dono unbounded. Bas
minvsmax, aur1 +vsval[i] +.)
Rod Cutting
GFG · Phase 3 ka aakhri — aur ye Q23 hi hai
Problem
N length ka ek rod hai. price[] array diya hai jahan price[i] = length (i+1) ke tukde ki keemat. Rod ko tukdon me kaato taaki total keemat maximum ho.
price = [2, 5, 7, 8, 10], N = 5 → 12
Kaise? Length 2 ka tukda (5) + length 2 ka tukda (5) + length 1 ka tukda (2) = 5+5+2 = 12 ✅ (Poora rod ek saath bechte toh sirf 10 milta.)
Soch — naam badla, sawaal wahi
Ye Unbounded Knapsack hai. Bas:
- weight = tukde ki length =
i + 1 - value =
price[i] - capacity =
N(rod ki poori length)
Aur "unbounded" kyun? Kyunki ek hi length ke tukde kitne bhi kaat sakte ho — length 2 ke do tukde, teen tukde, jitne fit ho jaayein.
Toh recursion wahi Q23 wala:
notTake = 0 + f(i - 1, N)
take = price[i] + f(i, N - (i + 1)) // i same, length = i+1
return max(take, notTake)
Base case
if (i == 0) {
return N * price[0]; // length-1 ke tukde, N tukde bante hain
}
Sirf length-1 wale tukde available hain. Rod length N hai, toh exactly N tukde banenge. Q23 ka (W / wt[0]) * val[0] yahan (N / 1) * price[0] = N * price[0] ban gaya. ✅
Code
static int f(int i, int N, int[] price, int[][] dp) {
if (i == 0) return N * price[0];
if (dp[i][N] != -1) return dp[i][N];
int notTake = 0 + f(i - 1, N, price, dp);
int take = Integer.MIN_VALUE;
int rodLength = i + 1;
if (rodLength <= N) {
take = price[i] + f(i, N - rodLength, price, dp); // i same
}
return dp[i][N] = Math.max(take, notTake);
}
// driver: n = price.length (= N)
int[][] dp = new int[n][N + 1];
for (int[] row : dp) Arrays.fill(row, -1);
System.out.println(f(n - 1, N, price, dp));
static int f(int n, int[] price) {
int N = n;
int[][] dp = new int[n][N + 1];
for (int len = 0; len <= N; len++) {
dp[0][len] = len * price[0];
}
for (int i = 1; i < n; i++) {
for (int len = 0; len <= N; len++) {
int notTake = 0 + dp[i - 1][len];
int take = Integer.MIN_VALUE;
int rodLength = i + 1;
if (rodLength <= len) take = price[i] + dp[i][len - rodLength];
dp[i][len] = Math.max(take, notTake);
}
}
return dp[n - 1][N];
}
static int f(int n, int[] price) {
int N = n;
int[] dp = new int[N + 1];
for (int len = 0; len <= N; len++) dp[len] = len * price[0];
for (int i = 1; i < n; i++) {
for (int len = 0; len <= N; len++) { // SEEDHA (unbounded)
int notTake = dp[len];
int take = Integer.MIN_VALUE;
int rodLength = i + 1;
if (rodLength <= len) take = price[i] + dp[len - rodLength];
dp[len] = Math.max(take, notTake);
}
}
return dp[N];
}
Dry Run
price = [2, 5, 7, 8, 10], N = 5
Base row (price[0] = 2, length 1): dp[0][len] = len * 2
| i \ len | 0 | 1 | 2 | 3 | 4 | 5 |
|---|---|---|---|---|---|---|
| 0 len1 ₹2 | 0 | 2 | 4 | 6 | 8 | 10 |
| 1 len2 ₹5 | 0 | 2 | 5 | 7 | 10 | 12 |
| 2 len3 ₹7 | 0 | 2 | 5 | 7 | 10 | 12 |
| 3 len4 ₹8 | 0 | 2 | 5 | 7 | 10 | 12 |
| 4 len5 ₹10 | 0 | 2 | 5 | 7 | 10 | 12 |
Row 1 — length-2 tukde available hue
| len | notTake = dp[0][len] | take = 5 + dp[1][len−2] | dp[1][len] |
|---|---|---|---|
| 2 | 4 | 5 + dp[1][0] = 5 ✅ | 5 |
| 3 | 6 | 5 + dp[1][1] = 5+2 = 7 ✅ | 7 |
| 4 | 8 | 5 + dp[1][2] = 5+5 = 10 ✅ | 10 |
| 5 | 10 | 5 + dp[1][3] = 5+7 = 12 ✅ | 12 |
len = 4 pe dp[1][2] = 5 use hua — jisme ek length-2 tukda already hai. Toh do length-2 tukde. Unbounded. 🔁
len = 5 pe: 5 + dp[1][3], aur dp[1][3] = 7 khud 5 + 2 se aaya. Chain: 2 + 2 + 1 lengths → 5 + 5 + 2 = ₹12 ✅
Rows 2, 3, 4 — kuch nahi badla
Length 3 (₹7), 4 (₹8), 5 (₹10) — teeno ka per-unit rate length-2 (₹2.5/unit) se kam hai. Toh DP unko kabhi chunta hi nahi. Table bilkul freeze ho gayi.
Ye acchi sanity check hai: agar tumhari table aakhri rows me badal rahi hai jabki wo items clearly kharab hain, toh take ke index me bug hai.
Complexity
| Approach | Time | Space |
|---|---|---|
| Memoization | O(N²) | O(N²) + O(N) stack |
| Tabulation | O(N²) | O(N²) |
| Space Optimized | O(N²) | O(N) |
n = N hai (price array ki length = rod length), isliye O(n × W) yahan O(N²) ban jaata hai.
Khud try karo
- Kaunse tukde kaate, wo print karo:
[2, 2, 1]. price = [3, 5, 8, 9, 10],N = 5pe chalao. Ab length-1 ka rate ₹3/unit hai — sabse acha. Answer 15 aana chahiye (paanch length-1 tukde).- Q23 ka code lo aur
wt[i] = i + 1bhar ke Q24 chalao. Bilkul same answer aana chahiye — kyunki ye ek hi sawaal hai.
Phase 4 · Q25–Q34
DP on Strings
Do strings, do pointers. Har step pe ek hi sawaal: aakhri characters match kar rahe hain ya nahi? Match hua toh dono pointer peeche; nahi hua toh ek-ek karke dono possibilities try karo. Poora Phase 4 isi ek if-else pe khada hai.
// i = s1 ka index, j = s2 ka index
if (s1[i] == s2[j])
return MATCH_VALUE + f(i-1, j-1)
return COMBINE( f(i-1, j), f(i, j-1) )
// base: i < 0 || j < 0 → string khatam
Longest Common Subsequence
LeetCode 1143 · Phase 4 ki neev — agle 9 sawaal isi pe khade hain
Problem
Do strings s1 aur s2 diye hain. Sabse lambi common subsequence ki length nikalo.
Subsequence = characters ka order same rahe, par beech ke characters chhod sakte ho. (Substring me continuous hona zaroori hai — wo Q27 me.)
s1 = "abcde", s2 = "ace" → 3 ("ace")
Soch — do index kyun chahiye
Phase 3 me state tha (index, target). Yahan do alag strings hain, aur dono me alag-alag jagah pe khade ho sakte ho. Toh state = do index.
f(i, j) = "s1[0..i] aur s2[0..j] ki LCS length"
Har step pe do case
Case 1 — s1[i] == s2[j]
Ye character zaroor LCS me hoga. Toh 1 gino aur dono pointer ek-ek peeche kar do.
return 1 + f(i - 1, j - 1);
Agar dono characters same hain, toh unko match karke chhodna kabhi behtar nahi ho sakta. Skip karoge toh answer sirf barabar ya kam hoga, zyada kabhi nahi. Isliye yahan koi choice hi nahi — seedha match kar lo. Ye ek greedy step hai jo DP ke andar safe hai.
Case 2 — s1[i] != s2[j]
Ab dono ko ek saath match nahi kar sakte. Toh ek-ek karke chhodo aur best lo:
return Math.max( f(i - 1, j), // s1 ka character chhoda
f(i, j - 1) ); // s2 ka character chhoda
Dono ko ek saath chhodne ki zaroorat nahi — wo case in dono ke andar already cover ho jaata hai.
Base case
if (i < 0 || j < 0) return 0;
Koi ek string khatam ho gayi → aage common kuch nahi mil sakta.
Shifted Index — tabulation ka sabse zaroori trick
Recursion me base case i < 0 hai — matlab dp[-1][...]. Par arrays me negative index hota hi nahi. Java crash kar dega.
Solution: poori table ko ek se right shift kar do.
| Recursion | Tabulation |
|---|---|
i | i + 1 |
i = -1 (khatam) | i = 0 (row of zeros) |
dp[n-1][m-1] = answer | dp[n][m] = answer |
table size n × m | table size (n+1) × (m+1) |
Ab pehli row aur pehla column poore zeros hain — wahi humara "string khatam" wala base case hai, aur Java me arrays default 0 se bhare hi hote hain. Free base case. 👌
Har string DP problem me shifted index use hoga. Ek baar samajh gaye toh Q26 se Q34 tak mechanical hai. Bas yaad rakhna: table me i ka matlab hai "string ka pehla i characters", aur character access karte waqt s1.charAt(i - 1) likhna padega.
Code
static int f(int i, int j, String s1, String s2) {
if (i < 0 || j < 0) return 0;
if (s1.charAt(i) == s2.charAt(j)) {
return 1 + f(i - 1, j - 1, s1, s2);
}
return Math.max(f(i - 1, j, s1, s2),
f(i, j - 1, s1, s2));
}
// call: f(n - 1, m - 1, s1, s2)
static int f(int i, int j, String s1, String s2, int[][] dp) {
if (i < 0 || j < 0) return 0;
if (dp[i][j] != -1) return dp[i][j];
if (s1.charAt(i) == s2.charAt(j)) {
return dp[i][j] = 1 + f(i - 1, j - 1, s1, s2, dp);
}
return dp[i][j] = Math.max(f(i - 1, j, s1, s2, dp),
f(i, j - 1, s1, s2, dp));
}
// driver
int[][] dp = new int[n][m];
for (int[] row : dp) Arrays.fill(row, -1);
System.out.println(f(n - 1, m - 1, s1, s2, dp));
static int lcs(String s1, String s2) {
int n = s1.length(), m = s2.length();
int[][] dp = new int[n + 1][m + 1]; // ⚠️ +1 dono me
// base case free hai: row 0 aur column 0 already 0 hain
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
// ⚠️ charAt me -1
if (s1.charAt(i - 1) == s2.charAt(j - 1)) {
dp[i][j] = 1 + dp[i - 1][j - 1];
} else {
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
return dp[n][m];
}
static int lcs(String s1, String s2) {
int n = s1.length(), m = s2.length();
int[] prev = new int[m + 1];
for (int i = 1; i <= n; i++) {
int[] curr = new int[m + 1];
for (int j = 1; j <= m; j++) {
if (s1.charAt(i - 1) == s2.charAt(j - 1)) {
curr[j] = 1 + prev[j - 1]; // diagonal
} else {
curr[j] = Math.max(prev[j], // up
curr[j - 1]); // left
}
}
prev = curr;
}
return prev[m];
}
Q8 me jo mapping seekhi thi, wahi yahan bhi: dp[i-1][j-1] → prev[j-1] (diagonal), dp[i-1][j] → prev[j] (up), dp[i][j-1] → curr[j-1] (left). Phase 2 ka gyaan yahan bina badle kaam aa raha hai.
Dry Run
s1 = "abcde" (n=5), s2 = "ace" (m=3)
| ∅ | a | c | e | |
|---|---|---|---|---|
| ∅ | 0 | 0 | 0 | 0 |
| a | 0 | 1 | 1 | 1 |
| b | 0 | 1 | 1 | 1 |
| c | 0 | 1 | 2 | 2 |
| d | 0 | 1 | 2 | 2 |
| e | 0 | 1 | 2 | 3 |
Cell by cell — kuch important wale
| Cell | s1 char | s2 char | Match? | Calculation | Value |
|---|---|---|---|---|---|
| dp[1][1] | a | a | ✅ | 1 + dp[0][0] = 1+0 | 1 |
| dp[2][1] | b | a | ❌ | max(dp[1][1], dp[2][0]) = max(1,0) | 1 |
| dp[3][2] | c | c | ✅ | 1 + dp[2][1] = 1+1 | 2 |
| dp[4][3] | d | e | ❌ | max(dp[3][3], dp[4][2]) = max(2,2) | 2 |
| dp[5][3] | e | e | ✅ | 1 + dp[4][2] = 1+2 | 3 |
Table ko padho
- Match wale cells hamesha diagonal se value lete hain aur
+1karte hain. Table me dekho:(a,a),(c,c),(e,e)— teen diagonal jumps, teen ki LCS. - No-match cells apne upar ya left se value copy karte hain — kuch naya add nahi hota.
- Values kabhi ghatti nahi, na neeche jaate hue na right jaate hue. Aur adjacent cells me farak 0 ya 1 hi ho sakta hai — ek character se zyada ka jump possible hi nahi.
Complexity
| Approach | Time | Space |
|---|---|---|
| Recursion | O(2^(n+m)) | O(n+m) stack |
| Memoization | O(n × m) | O(n × m) + O(n+m) |
| Tabulation | O(n × m) | O(n × m) |
| Space Optimized | O(n × m) | O(m) |
prev ke liye chhoti string chuno — O(min(n,m)) space. Interview me ye mention karna.
Khud try karo
s1 = "abc",s2 = "xyz"→ 0. Table poori zeros se bharegi.s1 = s2 = "abcde"→ 5. Poori diagonal bharegi.- Space-optimized me
curr[j-1]ki jagah galti seprev[j-1]likh do — kaunsa case tootega? (No-match wala. Aur answer chhota aayega.)
Print the LCS
GFG · backtracking on the dp table
Problem
Sirf LCS ki length nahi, actual string print karo.
s1 = "abcde", s2 = "ace" → "ace"
Soch — naya DP nahi, table padhna hai
Q25 ki table pehle se hi poora answer rakhti hai. Bas usko ulta chalna hai — dp[n][m] se shuru karke dp[0][0] tak.
Har cell pe ek hi sawaal: "ye value kahan se aayi thi?"
| Condition | Matlab | Kya karo |
|---|---|---|
s1[i-1] == s2[j-1] | ye character LCS me hai | character add karo, i--, j-- |
dp[i-1][j] > dp[i][j-1] | upar se aayi thi | i-- |
| warna | left se aayi thi | j-- |
Kyunki dp[n][m] hi wo cell hai jisme poora answer hai. Aage se peeche chalne se hum us exact raste pe chalte hain jisse ye value bani thi. Aur isliye string ulti banti hai — end me reverse karna padta hai (ya StringBuilder me aage se bharo).
Code
static String printLCS(String s1, String s2) {
int n = s1.length(), m = s2.length();
// ---- Step 1: Q25 wali table bana lo ----
int[][] dp = new int[n + 1][m + 1];
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (s1.charAt(i - 1) == s2.charAt(j - 1))
dp[i][j] = 1 + dp[i - 1][j - 1];
else
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
}
}
// ---- Step 2: peeche se chalo ----
StringBuilder sb = new StringBuilder();
int i = n, j = m;
while (i > 0 && j > 0) {
if (s1.charAt(i - 1) == s2.charAt(j - 1)) {
sb.append(s1.charAt(i - 1)); // LCS ka hissa
i--; j--; // diagonal
}
else if (dp[i - 1][j] > dp[i][j - 1]) {
i--; // upar se aaya
}
else {
j--; // left se aaya
}
}
return sb.reverse().toString(); // ulta bana tha
}
Backtracking ke liye poori table chahiye, sirf ek row se kaam nahi chalega. Ye ek general rule hai: "path print karna hai" = poori dp table rakho. Q3, Q10, Q11 me bhi yahi baat thi.
Dry Run
Q25 wali table (s1 = "abcde", s2 = "ace"). i = 5, j = 3 se shuru.
| Step | i, j | s1[i-1] | s2[j-1] | Match? | Action | sb |
|---|---|---|---|---|---|---|
| 1 | 5, 3 | e | e | ✅ | add 'e', i→4, j→2 | "e" |
| 2 | 4, 2 | d | c | ❌ | dp[3][2]=2 > dp[4][1]=1 → i→3 | "e" |
| 3 | 3, 2 | c | c | ✅ | add 'c', i→2, j→1 | "ec" |
| 4 | 2, 1 | b | a | ❌ | dp[1][1]=1, dp[2][0]=0 → i→1 | "ec" |
| 5 | 1, 1 | a | a | ✅ | add 'a', i→0, j→0 | "eca" |
| 6 | 0, 0 | loop khatam (i == 0) | "eca" | |||
sb.reverse() → "ace" ✅
Path ko table pe dekho
Backtrack ne exactly teen diagonal jumps liye — (5,3), (3,2), (1,1). Aur wahi teen cells the jinme Q25 ki dry run me match hua tha. Table ke andar chhupa hua path ab dikh gaya.
Khud try karo
- Tie hone pe (
dp[i-1][j] == dp[i][j-1]) codej--chunta hai.i--kar do — kya LCS badal jaayegi? (Length wahi rahegi, par kaunsi LCS mili wo badal sakti hai, agar multiple LCS hain.) s1 = "abcbdab",s2 = "bdcaba"— yahan teen alag LCS hain length 4 ki. Kaunsi milti hai?- Saari LCS print karne ka code likho (recursion + set). Bahut zyada ho sakti hain, isliye chhoti strings pe test karna.
Longest Common Substring
GFG · ek shabd badla, poora recurrence badal gaya
Problem
Sabse lambi common substring ki length. Substring = continuous characters, gap allowed nahi.
s1 = "abcjklp", s2 = "acjkp" → 3 ("cjk")
LCS hoti toh "acjkp" = 5 aati. Par continuous nahi hai, isliye substring me nahi chalega.
Soch — do bade badlaav
1 · No-match pe reset karo
LCS me mismatch pe hum max(up, left) lete the — matlab "purana progress bacha ke rakho, aage dekho".
Substring me mismatch ka matlab hai chain toot gayi. Purana progress ab bekaar hai. Toh dp[i][j] = 0.
2 · Answer last cell me nahi hota
LCS me answer dp[n][m] tha. Yahan nahi — kyunki dp[i][j] ab matlab rakhta hai "i aur j pe khatam hone wali common substring ki length".
Sabse lambi substring kahin beech me khatam ho sakti hai. Toh poori table ka maximum track karo.
// LCS (Q25)
if (match) dp[i][j] = 1 + dp[i-1][j-1];
else dp[i][j] = max(dp[i-1][j], dp[i][j-1]);
answer = dp[n][m];
// Longest Common Substring (Q27)
if (match) dp[i][j] = 1 + dp[i-1][j-1];
else dp[i][j] = 0; // ⚠️ reset
answer = max over ALL cells; // ⚠️ scan
Is problem me recursion natural nahi baithti, kyunki "yahan khatam hone wali chain" wala matlab top-down me express karna ganda ho jaata hai. Ye un chhote se problems me se hai jahan seedha tabulation likhna hi sahi hai. Interview me bhi yahi expected hai.
Code
static int longestCommonSubstring(String s1, String s2) {
int n = s1.length(), m = s2.length();
int[][] dp = new int[n + 1][m + 1];
int ans = 0;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (s1.charAt(i - 1) == s2.charAt(j - 1)) {
dp[i][j] = 1 + dp[i - 1][j - 1];
ans = Math.max(ans, dp[i][j]); // har cell pe check
} else {
dp[i][j] = 0; // chain toot gayi
}
}
}
return ans;
}
static int longestCommonSubstring(String s1, String s2) {
int n = s1.length(), m = s2.length();
int[] prev = new int[m + 1];
int ans = 0;
for (int i = 1; i <= n; i++) {
int[] curr = new int[m + 1];
for (int j = 1; j <= m; j++) {
if (s1.charAt(i - 1) == s2.charAt(j - 1)) {
curr[j] = 1 + prev[j - 1];
ans = Math.max(ans, curr[j]);
}
// else: curr[j] already 0 (fresh array)
}
prev = curr;
}
return ans;
}
No-match pe kuch likhna hi nahi — curr ek fresh array hai jo Java me 0 se bhare hue aata hai. Reset apne aap ho jaata hai. Aur sirf prev[j-1] (diagonal) chahiye, curr[j-1] kabhi nahi — isliye order ki tension bhi nahi.
Dry Run
s1 = "abcjklp", s2 = "acjkp"
| ∅ | a | c | j | k | p | |
|---|---|---|---|---|---|---|
| ∅ | 0 | 0 | 0 | 0 | 0 | 0 |
| a | 0 | 1 | 0 | 0 | 0 | 0 |
| b | 0 | 0 | 0 | 0 | 0 | 0 |
| c | 0 | 0 | 1 | 0 | 0 | 0 |
| j | 0 | 0 | 0 | 2 | 0 | 0 |
| k | 0 | 0 | 0 | 0 | 3 | 0 |
| l | 0 | 0 | 0 | 0 | 0 | 0 |
| p | 0 | 0 | 0 | 0 | 0 | 1 |
Diagonal chain ko dekho
| Cell | chars | Match? | Value | Chain |
|---|---|---|---|---|
| dp[3][2] | c, c | ✅ | 1 + dp[2][1] = 1+0 = 1 | "c" |
| dp[4][3] | j, j | ✅ | 1 + dp[3][2] = 1+1 = 2 | "cj" |
| dp[5][4] | k, k | ✅ | 1 + dp[4][3] = 1+2 = 3 | "cjk" |
| dp[6][5] | l, p | ❌ | 0 — reset | toot gayi |
| dp[7][5] | p, p | ✅ | 1 + dp[6][4] = 1+0 = 1 | "p" (nayi chain) |
Answer = 3 ✅ — aur wo dp[7][5] pe nahi, dp[5][4] pe mila. Isiliye poori table scan karni padti hai.
Table ko padho
LCS ki table bhari hui dikhti hai (values phailti hain). Substring ki table khaali dikhti hai, sirf chhote-chhote diagonal streaks ke saath. Har streak ek common substring hai. Sabse lambi streak = answer. Ye visual farak dono problems ko yaad rakhne ka sabse aasan tareeka hai.
Khud try karo
- Actual substring print karo, sirf length nahi. Hint: max value ka cell
(i, j)yaad rakho, phirs1.substring(i - ans, i). s1 = "abcd",s2 = "abcd"→ 4. Poori diagonal1,2,3,4bharegi.- Q25 ka code lo aur sirf
elsewali line badal do. Dono answers same input pe compare karo — ek line ka farak kitna bada hai, wo mehsoos hoga.
Longest Palindromic Subsequence
LeetCode 516 · ek line ka reduction
Problem
String s ki sabse lambi palindromic subsequence ki length.
s = "bbbab" → 4 ("bbbb")
s = "cbbd" → 2 ("bb")
Soch — palindrome ka matlab kya hai
Palindrome wo hota hai jo ulta likhne pe bhi wahi rehta hai.
Toh agar s ki koi subsequence palindrome hai, toh wo reverse(s) me bhi subsequence hogi — kyunki palindrome ulta karne pe badalta hi nahi.
LPS(s) = LCS(s, reverse(s))
Kyunki jo cheez dono me common subsequence hai aur ek doosre ka reverse hai — wo palindrome hi ho sakti hai.
Ek chhota sa doubt: kya LCS koi non-palindrome de sakta hai?
Ye sawaal thoda subtle hai. Formal proof lamba hai, par intuition ye hai: s aur reverse(s) me common subsequence banane ka matlab hai ki characters ka wahi order dono taraf se padha ja sakta hai — jo palindrome ki definition hi hai. Interview me itna bolna kaafi hai; formal proof shayad hi maanga jaaye.
Code
static int longestPalindromeSubseq(String s) {
String rev = new StringBuilder(s).reverse().toString();
return lcs(s, rev); // Q25 ka function, jaisa ka waisa
}
static int lcs(String s1, String s2) {
int n = s1.length(), m = s2.length();
int[] prev = new int[m + 1];
for (int i = 1; i <= n; i++) {
int[] curr = new int[m + 1];
for (int j = 1; j <= m; j++) {
if (s1.charAt(i - 1) == s2.charAt(j - 1))
curr[j] = 1 + prev[j - 1];
else
curr[j] = Math.max(prev[j], curr[j - 1]);
}
prev = curr;
}
return prev[m];
}
Dry Run
s = "bbbab", rev = "babbb"
| ∅ | b | a | b | b | b | |
|---|---|---|---|---|---|---|
| ∅ | 0 | 0 | 0 | 0 | 0 | 0 |
| b | 0 | 1 | 1 | 1 | 1 | 1 |
| b | 0 | 1 | 1 | 2 | 2 | 2 |
| b | 0 | 1 | 1 | 2 | 3 | 3 |
| a | 0 | 1 | 2 | 2 | 3 | 3 |
| b | 0 | 1 | 2 | 3 | 3 | 4 |
Answer = dp[5][5] = 4 ✅ → "bbbb"
Verify karo
s = "bbbab" me b chaar baar hai (index 0,1,2,4). Un chaaron ko lo → "bbbb" — palindrome ✅. a ko shaamil karte toh "bbabb"… par s me a ke baad sirf ek b hai, toh wo subsequence banti hi nahi. LCS ne sahi pakda.
Interval DP se bhi hota hai: f(i, j) = "s[i..j] ki LPS". Match pe 2 + f(i+1, j-1), warna max(f(i+1, j), f(i, j-1)). Complexity wahi O(n²). Par LCS reduction likhna 3 line ka kaam hai, aur Q29–Q31 me bhi kaam aata hai — isliye yahi seekho.
Khud try karo
s = "abcde"→ 1 (koi bhi single character palindrome hai).- Actual palindrome print karo — Q26 ka backtracking laga do.
- Interval DP wala version khud likho (
f(i,j)wala). Dono ke answers match hone chahiye.
Minimum Insertions to Make Palindrome
LeetCode 1312 · Q28 se ek subtraction
Problem
String s me kahin bhi characters insert kar sakte ho. Minimum kitne insertions se s palindrome ban jaayegi?
s = "abcaa" → 2
s = "mbadm" → 2 ("mdbabdm" ya "mbdadbm")
Soch — ulta karke socho
Seedha sochne me confusion hoti hai: "kahan-kahan insert karun?" Isliye ulta socho.
String ka jo hissa already palindrome hai, usko chhedne ki zaroorat nahi. Baaki har character ke liye ek mirror partner insert karna padega.
Sabse bada "already palindrome" hissa = LPS (Q28).
Toh: answer = n - LPS(s)
Har wo character jo LPS me nahi hai, uska jodidaar insert karna padega — exactly ek insertion per character.
Code
static int minInsertions(String s) {
int n = s.length();
String rev = new StringBuilder(s).reverse().toString();
int lps = lcs(s, rev); // Q28 = Q25 pe reverse
return n - lps;
}
Dry Run
s = "mbadm", n = 5
rev = "mdabm"
LCS("mbadm", "mdabm") = 3 → "mbm" ya "mam" ya "mdm"
answer = 5 - 3 = 2 ✅
Verify karo — kaunse 2 insertions
LPS = "mbm" (indices 0, 1, 4). Bache hue: a (index 2), d (index 3).
Dono ke mirror insert karo:
| Step | String | Palindrome? |
|---|---|---|
| original | m b a d m | ❌ |
| + 'd' | m b d a d m | ❌ |
| + 'a' | m b d a d b m | ✅ |
Ruko — "mbdadbm" me maine ek b bhi add kar diya, toh 3 insertions ho gaye. Sahi answer 2 hai, toh alag jagah insert karna hoga: "mbadabm"? Ye bhi palindrome nahi.
Sahi: LPS "mam" lo (indices 0, 2, 4). Bache: b, d. Result: "mbdadbm" — hmm ye 7 characters hai, matlab 2 insertions ✅ (5 → 7). Aur ye palindrome hai: m-b-d-a-d-b-m ✅
Formula bharosemand hai, par kahan insert karna hai wo formula nahi batata. Interview me answer n - LPS hi maanga jaata hai; actual string banane ke liye Q31 (Shortest Common Supersequence) ka backtracking chahiye hoga.
Khud try karo
s = "abcd"→ LPS = 1, answer 3. ("abcdcba"banana padega.)s = "aaa"→ LPS = 3, answer 0. Already palindrome.- Minimum deletions to make palindrome — wo bhi
n - LPShi hai! Socho kyun. (Hint: LPS ke bahar wale characters ya toh insert karo ya delete karo — ginti same.)
Minimum Insertions & Deletions to Convert A to B
GFG · LCS ka teesra reduction
Problem
String s1 ko s2 banana hai. Sirf do operations allowed: insert aur delete (replace nahi!). Minimum operations?
s1 = "abcd", s2 = "anc" → 3
Soch
Dono strings me jo common hai, usko chhedne ki zaroorat hi nahi. Wo LCS hai.
| Step | Kya karo | Kitne operations |
|---|---|---|
| 1 | s1 se wo sab delete karo jo LCS me nahi hai | n − LCS |
| 2 | ab sirf LCS bachi — usme wo sab insert karo jo s2 me hai | m − LCS |
total = (n - LCS) + (m - LCS)
= n + m - 2 * LCS
LCS wo "sabse chhota common ground" hai jahan dono strings mil sakti hain. Pehle wahan pahuncho (deletions), phir wahan se target tak jao (insertions). Ye do-step wali soch kai string problems me kaam aati hai.
Code
static int minOperations(String s1, String s2) {
int n = s1.length(), m = s2.length();
int lcsLen = lcs(s1, s2); // Q25
int deletions = n - lcsLen;
int insertions = m - lcsLen;
return deletions + insertions; // = n + m - 2*lcsLen
}
Dry Run
s1 = "abcd" (n=4), s2 = "anc" (m=3)
| ∅ | a | n | c | |
|---|---|---|---|---|
| ∅ | 0 | 0 | 0 | 0 |
| a | 0 | 1 | 1 | 1 |
| b | 0 | 1 | 1 | 1 |
| c | 0 | 1 | 1 | 2 |
| d | 0 | 1 | 1 | 2 |
LCS = 2 ("ac")
- Deletions =
4 - 2= 2 →baurdhataao - Insertions =
3 - 2= 1 →ndaalo - Total = 3 ✅
Step by step verify
| Operation | String |
|---|---|
| start | a b c d |
| delete 'b' | a c d |
| delete 'd' | a c ← ye LCS hai |
| insert 'n' | a n c ✅ |
Khud try karo
s1 = "abc",s2 = "abc"→ LCS = 3, answer 0.s1 = "abc",s2 = "xyz"→ LCS = 0, answer 6 (3 delete + 3 insert).- Agar replace bhi allowed ho toh? Wo Q33 (Edit Distance) hai — aur answer aksar chhota aayega. Dono compare karke dekho.
Shortest Common Supersequence
LeetCode 1092 · LCS + backtracking
Problem
Sabse chhoti string dhoondo jisme s1 aur s2 dono subsequence ke roop me maujood hon.
s1 = "brute", s2 = "groot" → "bgrouote" (length 8)
Soch — length pehle
Seedha jodo toh n + m characters lagenge. Par jo characters common hain, unhe ek hi baar likhna kaafi hai.
Sabse zyada kitne share kar sakte hain? LCS jitne.
length = n + m - LCS(s1, s2)
"brute" (5) + "groot" (5), LCS = "rot" (2? ya 3?) — dry run me exact nikalenge.
String banana — teen cases wala backtrack
Q26 me backtrack karke sirf match wale characters uthaate the. Yahan saare uthane hain — bas common wale ek hi baar.
| Condition | Kya karo | Kyun |
|---|---|---|
s1[i-1] == s2[j-1] | character add, i--, j-- | common — ek baar likho |
dp[i-1][j] > dp[i][j-1] | s1[i-1] add, i-- | sirf s1 ka, isko bhi chahiye |
| warna | s2[j-1] add, j-- | sirf s2 ka |
Jab i ya j me se ek 0 ho jaata hai, doosri string ke bache hue characters abhi bhi add karne hain. Do extra while loops lagenge. Ye bhoolna sabse common bug hai — answer chhota aa jaata hai.
Code
static String shortestCommonSupersequence(String s1, String s2) {
int n = s1.length(), m = s2.length();
// ---- Step 1: LCS table ----
int[][] dp = new int[n + 1][m + 1];
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (s1.charAt(i - 1) == s2.charAt(j - 1))
dp[i][j] = 1 + dp[i - 1][j - 1];
else
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
}
}
// ---- Step 2: backtrack, saare characters ----
StringBuilder sb = new StringBuilder();
int i = n, j = m;
while (i > 0 && j > 0) {
if (s1.charAt(i - 1) == s2.charAt(j - 1)) {
sb.append(s1.charAt(i - 1)); // common: ek baar
i--; j--;
}
else if (dp[i - 1][j] > dp[i][j - 1]) {
sb.append(s1.charAt(i - 1)); // sirf s1 ka
i--;
}
else {
sb.append(s2.charAt(j - 1)); // sirf s2 ka
j--;
}
}
// ---- Step 3: bache hue characters ----
while (i > 0) { sb.append(s1.charAt(i - 1)); i--; }
while (j > 0) { sb.append(s2.charAt(j - 1)); j--; }
return sb.reverse().toString();
}
Dry Run
s1 = "brute", s2 = "groot"
| ∅ | g | r | o | o | t | |
|---|---|---|---|---|---|---|
| ∅ | 0 | 0 | 0 | 0 | 0 | 0 |
| b | 0 | 0 | 0 | 0 | 0 | 0 |
| r | 0 | 0 | 1 | 1 | 1 | 1 |
| u | 0 | 0 | 1 | 1 | 1 | 1 |
| t | 0 | 0 | 1 | 1 | 1 | 2 |
| e | 0 | 0 | 1 | 1 | 1 | 2 |
LCS = 2 ("rt")
length = 5 + 5 - 2 = 8 ✅
Backtrack trace — i=5, j=5 se
| i, j | s1[i-1] | s2[j-1] | Case | Add | sb |
|---|---|---|---|---|---|
| 5, 5 | e | t | dp[4][5]=2 > dp[5][4]=1 → s1 | e | e |
| 4, 5 | t | t | match | t | et |
| 3, 4 | u | o | dp[2][4]=1, dp[3][3]=1 → s2 | o | eto |
| 3, 3 | u | o | dp[2][3]=1, dp[3][2]=1 → s2 | o | etoo |
| 3, 2 | u | r | dp[2][2]=1 > dp[3][1]=0 → s1 | u | etoou |
| 2, 2 | r | r | match | r | etoour |
| 1, 1 | b | g | dp[0][1]=0, dp[1][0]=0 → s2 | g | etoourg |
| 1, 0 | loop khatam (j == 0) | — | etoourg | ||
| tail | while (i > 0) → s1 ka 'b' bacha | b | etoourgb | ||
sb.reverse() → "bgruoote" — length 8 ✅
Verify karo
| String | Result me kahan |
|---|---|
| b g r u o o t e | — |
| brute | b g r u o o t e ✅ |
| groot | b g r u o o t e ✅ |
Dono subsequence hain, aur length 8 — minimum possible. ✅
Multiple valid answers ho sakte hain ("bgrouote" bhi sahi hai). LeetCode koi bhi valid answer accept karta hai.
Khud try karo
s1 = "abc",s2 = "abc"→"abc", length 3.s1 = "abc",s2 = "xyz"→ length 6, LCS = 0.- Do tail loops hata do — kya galat aata hai?
s1 = "abc",s2 = "c"pe test karo.
Distinct Subsequences
LeetCode 115 (hard) · counting, aur match pe DO options
Problem
s1 me s2 kitni baar subsequence ke roop me aati hai? Count karo.
s1 = "babgbag", s2 = "bag" → 5
s1 = "rabbbit", s2 = "rabbit" → 3 (teen alag b chun sakte ho)
Soch — yahan match pe choice HAI
LCS me match hone pe humne kaha tha "match kar lo, choice hi nahi". Yahan wo galat hai.
Kyunki hum count kar rahe hain, aur ek hi character se banne wale alag-alag ways ko alag ginna hai.
f(i, j) = "s1[0..i] me s2[0..j] kitni baar subsequence hai"
Case 1 — s1[i] == s2[j]: do options
option A: is character ko match karo → f(i-1, j-1)
option B: is character ko chhod do,
aage kahin aur match dhoondo → f(i-1, j)
return A + B;
Case 2 — mismatch: ek hi option
return f(i - 1, j); // s1 ka character bekaar, chhod do
Kyunki s2 ka har character match hona hi hai — hum s2 ko poora dhoondh rahe hain, uska hissa chhod nahi sakte. LCS me dono strings barabar the; yahan s2 "target" hai. Isliye sirf s1 ka pointer ghoomta hai.
Base cases — order zaroori
if (j < 0) return 1; // s2 poori mil gayi ✅ ek tareeka
if (i < 0) return 0; // s1 khatam, s2 baaki ❌
j < 0 ka check pehle. Agar dono khatam hue toh wo success hai, failure nahi.
Code
static int f(int i, int j, String s1, String s2, int[][] dp) {
if (j < 0) return 1;
if (i < 0) return 0;
if (dp[i][j] != -1) return dp[i][j];
if (s1.charAt(i) == s2.charAt(j)) {
return dp[i][j] = f(i - 1, j - 1, s1, s2, dp) // match
+ f(i - 1, j, s1, s2, dp); // skip
}
return dp[i][j] = f(i - 1, j, s1, s2, dp);
}
static int numDistinct(String s1, String s2) {
int n = s1.length(), m = s2.length();
double[][] dp = new double[n + 1][m + 1]; // overflow se bachne ko
// base: s2 khaali → 1 tareeka (kuch mat lo)
for (int i = 0; i <= n; i++) dp[i][0] = 1;
// base: s1 khaali, s2 nahi → 0 (already 0)
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (s1.charAt(i - 1) == s2.charAt(j - 1)) {
dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];
} else {
dp[i][j] = dp[i - 1][j];
}
}
}
return (int) dp[n][m];
}
Counts exponentially badhte hain. LeetCode 115 guarantee karta hai ki answer int me fit hoga, par beech ke cells overflow kar sakte hain. Isliye double ya long use karo. Ye ek famous "sahi logic, galat answer" bug hai.
static int numDistinct(String s1, String s2) {
int n = s1.length(), m = s2.length();
double[] dp = new double[m + 1];
dp[0] = 1;
for (int i = 1; i <= n; i++) {
for (int j = m; j >= 1; j--) { // ⚠️ ULTA loop
if (s1.charAt(i - 1) == s2.charAt(j - 1)) {
dp[j] = dp[j - 1] + dp[j];
}
// else: dp[j] waisa hi rehta hai (= dp[i-1][j])
}
}
return (int) dp[m];
}
dp[j-1] ko pichli row ki value chahiye. Ulta chalne se wo abhi tak update nahi hui. Bilkul wahi trick jo Q15 aur Q19 me 0/1 knapsack ke liye lagayi thi — alag problem, same mechanic.
Dry Run
s1 = "babgbag", s2 = "bag"
| ∅ | b | a | g | |
|---|---|---|---|---|
| ∅ | 1 | 0 | 0 | 0 |
| b | 1 | 1 | 0 | 0 |
| a | 1 | 1 | 1 | 0 |
| b | 1 | 2 | 1 | 0 |
| g | 1 | 2 | 1 | 1 |
| b | 1 | 3 | 1 | 1 |
| a | 1 | 3 | 4 | 1 |
| g | 1 | 3 | 4 | 5 |
Kuch important cells
| Cell | chars | Match? | Calculation | Value |
|---|---|---|---|---|
| dp[3][1] | b, b | ✅ | dp[2][0] + dp[2][1] = 1 + 1 | 2 |
| dp[5][1] | b, b | ✅ | dp[4][0] + dp[4][1] = 1 + 2 | 3 |
| dp[6][2] | a, a | ✅ | dp[5][1] + dp[5][2] = 3 + 1 | 4 |
| dp[7][3] | g, g | ✅ | dp[6][2] + dp[6][3] = 4 + 1 | 5 |
Table ko padho
Column 0 poora 1 — khaali s2 banane ka hamesha ek hi tareeka: kuch mat lo. (Q22 ke dp[0] = 1 jaisa hi.)
Column b me values 1 → 2 → 3 badhti hain — s1 me har naya b ek naya tareeka jodta hai. Yahi + dp[i-1][j] wala "skip" term kar raha hai.
Aur diagonal ke upar sab zeros hain — s1 ka chhota prefix s2 ka bada prefix bana hi nahi sakta.
Paanch tareeke — verify
s1 = "b a b g b a g" (indices 0-6). b at 0,2,4 · a at 1,5 · g at 3,6
| # | b | a | g |
|---|---|---|---|
| 1 | 0 | 1 | 3 |
| 2 | 0 | 1 | 6 |
| 3 | 0 | 5 | 6 |
| 4 | 2 | 5 | 6 |
| 5 | 4 | 5 | 6 |
5 ✅
Khud try karo
doubleki jagahintdaal ke LeetCode 115 pe submit karo — kuch test cases fail honge. Wahi overflow hai.s1 = "abc",s2 = "abcd"→ 0. Table me diagonal ke upar wale zeros isko handle karte hain.- Match wale case me sirf
dp[i-1][j-1]rakho (skip term hata do) — kya milta hai? (Sirf ek specific matching, count nahi.)
Edit Distance
LeetCode 72 (hard) · Phase 4 ka sabse famous sawaal
Problem
s1 ko s2 banana hai. Teen operations allowed, har ek ka cost 1:
- Insert — koi character daalo
- Delete — koi character hataao
- Replace — koi character badlo
Minimum operations?
s1 = "horse", s2 = "ros" → 3
Soch
Q30 me sirf insert/delete the. Ab replace bhi hai — aur wo aksar do operations (delete + insert) ki jagah ek me kaam kar deta hai.
f(i, j) = "s1[0..i] ko s2[0..j] banane ka minimum cost"
Case 1 — s1[i] == s2[j]
Kuch karne ki zaroorat hi nahi. Cost 0, dono pointer peeche:
return 0 + f(i - 1, j - 1);
Case 2 — mismatch: teeno operations try karo
| Operation | Kya hota hai | Recursive call |
|---|---|---|
| Insert | s2[j] ko s1 me daal diya — wo match ho gaya | f(i, j-1) |
| Delete | s1[i] hata diya | f(i-1, j) |
| Replace | s1[i] ko s2[j] bana diya — dono match | f(i-1, j-1) |
return 1 + min( f(i, j - 1), // insert
f(i - 1, j), // delete
f(i - 1, j - 1) ); // replace
Ye sabse confusing part hai. Socho aise: humne s1 me ek naya character daala jo s2[j] se match karta hai.
Toh s2 ka wo character ho gaya (j--), par s1 ka original character s1[i] abhi bhi pending hai — use aage handle karna hai. Isliye i waisa ka waisa.
Delete me ulta: s1[i] khatam (i--), par s2[j] ab bhi chahiye.
Base cases
if (i < 0) return j + 1; // s1 khatam → s2 ke bache chars insert karo
if (j < 0) return i + 1; // s2 khatam → s1 ke bache chars delete karo
j + 1 kyunki index 0..j me j+1 characters hote hain.
Code
static int f(int i, int j, String s1, String s2, int[][] dp) {
if (i < 0) return j + 1;
if (j < 0) return i + 1;
if (dp[i][j] != -1) return dp[i][j];
if (s1.charAt(i) == s2.charAt(j)) {
return dp[i][j] = 0 + f(i - 1, j - 1, s1, s2, dp);
}
int insert = f(i, j - 1, s1, s2, dp);
int delete = f(i - 1, j, s1, s2, dp);
int replace = f(i - 1, j - 1, s1, s2, dp);
return dp[i][j] = 1 + Math.min(insert,
Math.min(delete, replace));
}
static int minDistance(String s1, String s2) {
int n = s1.length(), m = s2.length();
int[][] dp = new int[n + 1][m + 1];
// base: s2 khaali → s1 ke saare chars delete
for (int i = 0; i <= n; i++) dp[i][0] = i;
// base: s1 khaali → s2 ke saare chars insert
for (int j = 0; j <= m; j++) dp[0][j] = j;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (s1.charAt(i - 1) == s2.charAt(j - 1)) {
dp[i][j] = dp[i - 1][j - 1];
} else {
dp[i][j] = 1 + Math.min(dp[i][j - 1], // insert
Math.min(dp[i - 1][j], // delete
dp[i - 1][j - 1])); // replace
}
}
}
return dp[n][m];
}
Q25 me pehli row/column zeros the aur Java ne khud bhar diye the. Yahan wo 0, 1, 2, 3... hain — do explicit loops chahiye. Ye bhoolna common bug hai; answer chhota aayega.
static int minDistance(String s1, String s2) {
int n = s1.length(), m = s2.length();
int[] prev = new int[m + 1];
for (int j = 0; j <= m; j++) prev[j] = j;
for (int i = 1; i <= n; i++) {
int[] curr = new int[m + 1];
curr[0] = i; // ⚠️ har row me set karo
for (int j = 1; j <= m; j++) {
if (s1.charAt(i - 1) == s2.charAt(j - 1)) {
curr[j] = prev[j - 1];
} else {
curr[j] = 1 + Math.min(curr[j - 1],
Math.min(prev[j], prev[j - 1]));
}
}
prev = curr;
}
return prev[m];
}
Dry Run
s1 = "horse", s2 = "ros"
| ∅ | r | o | s | |
|---|---|---|---|---|
| ∅ | 0 | 1 | 2 | 3 |
| h | 1 | 1 | 2 | 3 |
| o | 2 | 2 | 1 | 2 |
| r | 3 | 2 | 2 | 2 |
| s | 4 | 3 | 3 | 2 |
| e | 5 | 4 | 4 | 3 |
Kuch important cells
| Cell | chars | Match? | Calculation | Value |
|---|---|---|---|---|
| dp[1][1] | h, r | ❌ | 1 + min(dp[1][0]=1, dp[0][1]=1, dp[0][0]=0) = 1+0 | 1 |
| dp[2][2] | o, o | ✅ | dp[1][1] = 1 — free | 1 |
| dp[3][1] | r, r | ✅ | dp[2][0] = 2 — free | 2 |
| dp[4][3] | s, s | ✅ | dp[3][2] = 2 — free | 2 |
| dp[5][3] | e, s | ❌ | 1 + min(dp[5][2]=4, dp[4][3]=2, dp[4][2]=3) = 1+2 | 3 |
Teen operations — verify
| Step | String | Operation |
|---|---|---|
| 0 | h o r s e | start |
| 1 | r o r s e | replace 'h' → 'r' |
| 2 | r o s e | delete 'r' (index 2) |
| 3 | r o s | delete 'e' ✅ |
Table ko padho
- Pehli row = 0,1,2,3 — khaali
s1se"ros"banane ke liye 3 inserts. - Pehla column = 0,1,2,3,4,5 —
"horse"ko khaali banane ke liye 5 deletes. - Match wale cells diagonal se seedha copy karte hain (
+1nahi) — free operation. Table me woo-o,r-r,s-spe dikh raha hai. - Adjacent cells me farak hamesha 0 ya 1 hi hota hai.
Same input pe Q30 (sirf insert/delete) deta: n + m - 2×LCS = 5 + 3 - 2×2 = 4. Edit Distance deta 3. Replace ne ek operation bacha liya.
Khud try karo
s1 = "intention",s2 = "execution"→ 5. Poori table banao.- Operations print karo, sirf count nahi. Backtrack karke dekho ki har cell kis case se aayi.
- Agar replace ka cost 2 ho (insert/delete ka 1) toh? Code me kya badlega? Aur kya answer Q30 ke barabar aa jaayega? (Haan — kyunki replace tab delete+insert se sasta nahi rahega.)
Wildcard Matching
LeetCode 44 (hard) · Phase 4 ka finale
Problem
Pattern p aur string s diye hain. Kya p poori s ko match karta hai?
?— exactly ek character match karta hai*— koi bhi sequence (khaali bhi) match karta hai
| s | p | Result |
|---|---|---|
| "aa" | "a" | false |
| "aa" | "*" | true |
| "cb" | "?a" | false |
| "adceb" | "*a*b" | true |
Soch — teen cases
f(i, j) = "kya p[0..j], s[0..i] ko match karta hai?" (i string ka, j pattern ka)
Case 1 — direct match ya ?
if (s.charAt(i) == p.charAt(j) || p.charAt(j) == '?')
return f(i - 1, j - 1);
? ek character kha jaata hai — bilkul normal match jaisa.
Case 2 — *: do options
if (p.charAt(j) == '*')
return f(i - 1, j) // * ne s[i] ko kha liya, aur bhi kha sakta hai
|| f(i, j - 1); // * ne khaali match kiya, aage badho
f(i-1, j) — * ne ek aur character nigal liya, par * abhi bhi zinda hai (j nahi ghata). Isse * jitne chahe utne characters kha sakta hai.
f(i, j-1) — * ne zero characters match kiye, ab pattern me aage badho.
Dono me se koi bhi true de de toh kaam ho gaya → ||.
Case 3 — mismatch
return false;
Base cases — yahan sabse zyada log fasste hain
if (i < 0 && j < 0) return true; // dono khatam ✅
if (j < 0) return false; // pattern khatam, string baaki ❌
if (i < 0) { // string khatam, pattern baaki
for (int k = 0; k <= j; k++)
if (p.charAt(k) != '*') return false;
return true; // sab '*' hain → khaali match ✅
}
String khatam ho gayi par pattern bacha hai — ye tabhi true hai jab bache hue saare characters * hon (kyunki * khaali match kar sakta hai). Ek bhi normal character ya ? bacha toh false. Ye check bhoolna sabse common bug hai.
Code
static boolean isAllStars(String p, int j) {
for (int k = 0; k <= j; k++)
if (p.charAt(k) != '*') return false;
return true;
}
static boolean f(int i, int j, String s, String p, int[][] dp) {
if (i < 0 && j < 0) return true;
if (j < 0) return false;
if (i < 0) return isAllStars(p, j);
if (dp[i][j] != -1) return dp[i][j] == 1;
boolean ans;
if (s.charAt(i) == p.charAt(j) || p.charAt(j) == '?') {
ans = f(i - 1, j - 1, s, p, dp);
}
else if (p.charAt(j) == '*') {
ans = f(i - 1, j, s, p, dp) || f(i, j - 1, s, p, dp);
}
else {
ans = false;
}
dp[i][j] = ans ? 1 : 0;
return ans;
}
static boolean isMatch(String s, String p) {
int n = s.length(), m = p.length();
boolean[][] dp = new boolean[n + 1][m + 1];
dp[0][0] = true; // dono khaali
// s khaali, p baaki → sab '*' hone chahiye
for (int j = 1; j <= m; j++) {
boolean allStars = true;
for (int k = 1; k <= j; k++)
if (p.charAt(k - 1) != '*') { allStars = false; break; }
dp[0][j] = allStars;
}
// p khaali, s baaki → dp[i][0] = false (default)
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (s.charAt(i - 1) == p.charAt(j - 1)
|| p.charAt(j - 1) == '?') {
dp[i][j] = dp[i - 1][j - 1];
}
else if (p.charAt(j - 1) == '*') {
dp[i][j] = dp[i - 1][j] || dp[i][j - 1];
}
else {
dp[i][j] = false;
}
}
}
return dp[n][m];
}
static boolean isMatch(String s, String p) {
int n = s.length(), m = p.length();
boolean[] prev = new boolean[m + 1];
prev[0] = true;
for (int j = 1; j <= m; j++) {
prev[j] = prev[j - 1] && p.charAt(j - 1) == '*';
}
for (int i = 1; i <= n; i++) {
boolean[] curr = new boolean[m + 1];
curr[0] = false; // p khaali, s baaki
for (int j = 1; j <= m; j++) {
if (s.charAt(i - 1) == p.charAt(j - 1)
|| p.charAt(j - 1) == '?')
curr[j] = prev[j - 1];
else if (p.charAt(j - 1) == '*')
curr[j] = prev[j] || curr[j - 1];
else
curr[j] = false;
}
prev = curr;
}
return prev[m];
}
Space-optimized me prev[j] = prev[j-1] && p.charAt(j-1) == '*' — ye nested loop ki jagah ek line me "ab tak sab star the?" track kar leta hai. Pehla non-star aate hi false ho jaata hai aur phir kabhi true nahi hota. Chhota par saaf trick.
Dry Run
s = "adceb", p = "*a*b"
| ∅ | * | a | * | b | |
|---|---|---|---|---|---|
| ∅ | T | T | F | F | F |
| a | F | T | T | T | F |
| d | F | T | F | T | F |
| c | F | T | F | T | F |
| e | F | T | F | T | F |
| b | F | T | F | T | T |
Kuch important cells
| Cell | s char | p char | Rule | Value |
|---|---|---|---|---|
| dp[0][1] | ∅ | * | base: sab star tak | T |
| dp[0][2] | ∅ | a | base: 'a' star nahi | F |
| dp[1][2] | a | a | match → dp[0][1] = T | T |
| dp[2][3] | d | * | dp[1][3]=T || dp[2][2]=F | T |
| dp[5][4] | b | b | match → dp[4][3] = T | T |
Answer = true ✅
Kaise match hua
* (pehla) → khaali · a → 'a' · * (doosra) → "dce" · b → 'b'
Table me column * (index 3) poora T hai row 1 se aage — kyunki wo star kitne bhi characters nigal sakta hai. Yahi dp[i-1][j] wali line kar rahi hai.
Table ko padho
* ke columns vertically phailte hain (ek baar T hua toh neeche sab T) — kyunki star aur characters kha sakta hai. Normal characters ke columns me T sirf ek specific cell pe aata hai. Ye visual pattern * ki taakat ka seedha dikhta hua roop hai.
🎉 Phase 4 complete
| # | Sawaal | Match pe | No match | Reduction |
|---|---|---|---|---|
| 25 | LCS | 1 + diag | max(up, left) | — |
| 26 | Print LCS | Q25 table pe backtrack | — | |
| 27 | Common Substring | 1 + diag | 0 (reset) | — |
| 28 | LPS | — | LCS(s, rev(s)) | |
| 29 | Min Insert Palindrome | — | n − LPS | |
| 30 | Insert/Delete A→B | — | n + m − 2·LCS | |
| 31 | Shortest Supersequence | 3-case backtrack | n + m − LCS | |
| 32 | Distinct Subsequences | diag + up | up only | — |
| 33 | Edit Distance | 0 + diag | 1 + min(3) | — |
| 34 | Wildcard Matching | diag | * → up || left | — |
Chaar sawaal (Q28–Q31) me naya DP likha hi nahi — sirf LCS ko ghumaya. Phase 3 ka wahi sabak yahan bhi.
Khud try karo
s = "aa",p = "*"→true. Aurs = "",p = "***"→true. Base cases test karo.- Teesra base case (
isAllStars) hata do — kaunsa test case tootega? (s = "a",p = "a*") - Regular Expression Matching — LeetCode 10 (hard). Wahan
*ka matlab alag hai: "pichla character zero ya zyada baar". Bahut zyada tedha hai — par agar Q34 samajh gaye toh wo bhi lag jaayega.
Phase 5 · Q35–Q40
DP on Stocks
Chhe sawaal, ek hi soch. Har din pe ek hi sawaal: "abhi mere paas share hai ya nahi?" — aur usi ke hisaab se do choices banti hain. Ye Q7 (Ninja's Training) wali soch hai: jo cheez decision ko affect karti hai, use state me daal do.
if (buy == 1)
return max( -prices[i] + f(i+1, 0), // khareed lo
0 + f(i+1, 1) ) // skip
return max( +prices[i] + f(i+1, 1), // bech do
0 + f(i+1, 0) ) // skip
// base: i == n → 0
Best Time to Buy and Sell Stock
LeetCode 121 · exactly 1 transaction
Problem
prices[i] = i-th din ka share ka daam. Ek baar khareedo aur ek baar becho (bechne se pehle khareedna zaroori). Maximum profit?
prices = [7, 1, 5, 3, 6, 4] → 5 (din 1 pe ₹1 me khareedo, din 4 pe ₹6 me becho)
prices = [7, 6, 4, 3, 1] → 0 (daam sirf girta hai — kuch mat karo)
Soch — ye DP hai bhi ya nahi?
Sirf ek transaction hai, toh sawaal simple ban jaata hai:
Har dinike liye: agar aaj bechun, toh maximum profit kitna? =prices[i] − (ab tak ka sabse kam daam)
Toh bas left se chalte jao aur do cheezein track karo: ab tak ka minimum price, aur ab tak ka maximum profit.
minPrice ek 1-state DP hai — "din i tak ka best buying point". Bas itna simple hai ki alag dp[] array ki zaroorat hi nahi, ek variable kaafi hai. Q1 me Fibonacci ka space-optimized version bhi to yahi tha.
Code
static int maxProfit(int[] prices) {
int minPrice = prices[0];
int maxProfit = 0;
for (int i = 1; i < prices.length; i++) {
// agar aaj bechein toh kitna profit
int profit = prices[i] - minPrice;
maxProfit = Math.max(maxProfit, profit);
// kal ke liye best buying point update karo
minPrice = Math.min(minPrice, prices[i]);
}
return maxProfit;
}
profit pehle nikalo, minPrice baad me update karo. Ulta karoge toh aaj ke din khareed ke aaj hi bech doge — profit hamesha 0. Logically galat, aur silently galat.
Dry Run
prices = [7, 1, 5, 3, 6, 4]
| i | price | minPrice (pehle) | profit = price − min | maxProfit | minPrice (baad me) |
|---|---|---|---|---|---|
| 0 | 7 | — | — | 0 | 7 |
| 1 | 1 | 7 | 1−7 = −6 | 0 | 1 |
| 2 | 5 | 1 | 5−1 = 4 | 4 | 1 |
| 3 | 3 | 1 | 3−1 = 2 | 4 | 1 |
| 4 | 6 | 1 | 6−1 = 5 | 5 | 1 |
| 5 | 4 | 1 | 4−1 = 3 | 5 | 1 |
Answer = 5 ✅
i = 1 pe profit negative aaya — par maxProfit 0 se shuru hua tha, isliye wo 0 hi raha. Yahi "kuch mat karo" wala option hai, automatically handled.
Khud try karo
- Kaunse din khareeda aur becha, wo bhi print karo. (Do extra variables track karne padenge.)
- Ye same sawaal Phase 5 wale
f(i, buy)template se likho, aur verify karo ki same answer aata hai. Us version me ek teesra state (cap = 1) chahiye hoga — wo Q37 ka warm-up hai. - Agar short selling allowed ho (pehle bech ke baad me khareedna)? Answer kya badlega?
Stock II — Infinite Transactions
LeetCode 122 · asli DP template yahan se shuru
Problem
Jitni chaaho utni transactions kar sakte ho. Par ek waqt me sirf ek share — bechne se pehle dobara nahi khareed sakte. Maximum profit?
prices = [7, 1, 5, 3, 6, 4] → 7
Khareedo ₹1 becho ₹5 (profit 4), khareedo ₹3 becho ₹6 (profit 3). Total 7 ✅
Soch — "buy" ko state banao
Har din pe kya kar sakte ho, ye is baat pe depend karta hai ki abhi share paas me hai ya nahi. Toh wahi state me daal do.
buy | Matlab | Do choices |
|---|---|---|
1 | share paas me nahi hai — khareed sakte ho | khareedo (−price) ya skip |
0 | share paas me hai — bech sakte ho | becho (+price) ya skip |
buy = 1 ka matlab hai "khareedne ki baari hai", na ki "khareed liya hai". Bahut log ulta samajh lete hain. Agar confusion ho toh canBuy naam de do.
f(i, buy) = "din i se aage, buy state me, maximum profit"
if (buy == 1)
return max( -prices[i] + f(i+1, 0), // khareeda → ab bech sakte ho
0 + f(i+1, 1) ); // skip
return max( +prices[i] + f(i+1, 1), // becha → ab khareed sakte ho
0 + f(i+1, 0) ); // skip
Khareedne pe -prices[i] (kharcha) aur bechne pe +prices[i] (kamai). End me sab jud ke net profit ban jaata hai. Alag se "kitne me khareeda tha" yaad rakhne ki zaroorat hi nahi — ye ek bahut saaf trick hai.
Base case
if (i == n) return 0; // din khatam, aage kamai nahi
Code
static long f(int i, int buy, int n, int[] prices, long[][] dp) {
if (i == n) return 0;
if (dp[i][buy] != -1) return dp[i][buy];
long profit;
if (buy == 1) {
profit = Math.max(-prices[i] + f(i + 1, 0, n, prices, dp),
0 + f(i + 1, 1, n, prices, dp));
} else {
profit = Math.max( prices[i] + f(i + 1, 1, n, prices, dp),
0 + f(i + 1, 0, n, prices, dp));
}
return dp[i][buy] = profit;
}
// driver
long[][] dp = new long[n][2];
for (long[] row : dp) Arrays.fill(row, -1);
System.out.println(f(0, 1, n, prices, dp));
static long maxProfit(int[] prices) {
int n = prices.length;
long[][] dp = new long[n + 1][2];
dp[n][0] = 0; // base case
dp[n][1] = 0;
for (int i = n - 1; i >= 0; i--) { // ULTA (recursion aage jaati thi)
for (int buy = 0; buy <= 1; buy++) {
long profit;
if (buy == 1) {
profit = Math.max(-prices[i] + dp[i + 1][0],
0 + dp[i + 1][1]);
} else {
profit = Math.max( prices[i] + dp[i + 1][1],
0 + dp[i + 1][0]);
}
dp[i][buy] = profit;
}
}
return dp[0][1];
}
static long maxProfit(int[] prices) {
int n = prices.length;
long aheadBuy = 0, aheadSell = 0; // dp[i+1][1], dp[i+1][0]
for (int i = n - 1; i >= 0; i--) {
long currBuy = Math.max(-prices[i] + aheadSell, aheadBuy);
long currSell = Math.max( prices[i] + aheadBuy, aheadSell);
aheadBuy = currBuy;
aheadSell = currSell;
}
return aheadBuy;
}
dp[i] sirf dp[i+1] pe depend hai, aur har row me sirf 2 cells hain. Toh 2 variables kaafi — O(1) space. Phase 1 ke prev/prev2 ka hi bada bhai hai.
Dry Run
prices = [7, 1, 5, 3, 6, 4], n = 6. Ulta chalte hain (i = 5 se 0).
| i | price | currBuy = max(−p + aheadSell, aheadBuy) | currSell = max(p + aheadBuy, aheadSell) |
|---|---|---|---|
| 6 | — | 0 (base) | 0 (base) |
| 5 | 4 | max(−4+0, 0) = 0 | max(4+0, 0) = 4 |
| 4 | 6 | max(−6+4, 0) = 0 | max(6+0, 4) = 6 |
| 3 | 3 | max(−3+6, 0) = 3 | max(3+0, 6) = 6 |
| 2 | 5 | max(−5+6, 3) = 3 | max(5+3, 6) = 8 |
| 1 | 1 | max(−1+8, 3) = 7 | max(1+3, 8) = 8 |
| 0 | 7 | max(−7+8, 7) = 7 | max(7+7, 8) = 14 |
Answer = currBuy at i=0 = 7 ✅
Trace karo kaunse din
i=1 pe currBuy = 7 aaya -1 + 8 se — matlab din 1 pe ₹1 me khareeda.
i=2 pe currSell = 8 aaya 5 + 3 se — din 2 pe ₹5 me becha, aur baaki 3 aage se.
i=3 pe currBuy = 3 aaya -3 + 6 se — din 3 pe ₹3 me khareeda.
i=4 pe currSell = 6 aaya 6 + 0 se — din 4 pe ₹6 me becha.
Total: (5-1) + (6-3) = 4 + 3 = 7 ✅
Infinite transactions me jahan bhi prices[i+1] > prices[i] ho, wo farak jod do — answer wahi aayega, O(n) me ek line me. Par interview me DP hi likhna, kyunki Q37–Q40 me greedy kaam nahi karta aur DP wahi ka wahi rehta hai.
Khud try karo
- Greedy version likho (
sum of positive diffs) aur DP se compare karo. Random arrays pe 1000 baar test karo. f(0, 0)se call karke dekho — kya milta hai? (Galat answer, kyunki shuruaat me share paas me nahi hota.)- Q35 ko is template se likho: teesra state
capadd karo aurcap = 1se shuru karo. Answer 5 aana chahiye.
Stock III — At Most 2 Transactions
LeetCode 123 (hard) · teesra state add hota hai
Problem
Zyada se zyada 2 transactions. Maximum profit?
prices = [3, 3, 5, 0, 0, 3, 1, 4] → 6
Khareedo ₹0 (din 3) becho ₹3 (din 5) → profit 3. Khareedo ₹1 (din 6) becho ₹4 (din 7) → profit 3. Total 6 ✅
Soch — wahi sawaal, phir se
"Decision lene ke liye mujhe kya-kya pata hona chahiye?"
- Kaunsa din hai →
i - Share paas me hai ya nahi →
buy - Kitni transactions bachi hain →
cap← naya!
Bina cap ke pata hi nahi chalega ki aur transaction kar sakte ho ya limit khatam ho gayi. Isliye state me daalna zaroori hai.
Transaction "poori" tab hoti hai jab bech diya. Toh cap bechne pe ghatega, khareedne pe nahi.
(Ulta convention bhi chalta hai — khareedne pe ghatao. Par ek hi chuno aur uspe tike raho, warna off-by-one bug pakka.)
if (buy == 1)
return max( -prices[i] + f(i+1, 0, cap),
0 + f(i+1, 1, cap) );
return max( +prices[i] + f(i+1, 1, cap - 1), // ⚠️ cap ghata
0 + f(i+1, 0, cap) );
Base cases — ab do hain
if (i == n) return 0; // din khatam
if (cap == 0) return 0; // transactions khatam
Code
static int f(int i, int buy, int cap, int n, int[] prices, int[][][] dp) {
if (i == n) return 0;
if (cap == 0) return 0;
if (dp[i][buy][cap] != -1) return dp[i][buy][cap];
int profit;
if (buy == 1) {
profit = Math.max(-prices[i] + f(i + 1, 0, cap, n, prices, dp),
0 + f(i + 1, 1, cap, n, prices, dp));
} else {
profit = Math.max( prices[i] + f(i + 1, 1, cap - 1, n, prices, dp),
0 + f(i + 1, 0, cap, n, prices, dp));
}
return dp[i][buy][cap] = profit;
}
// driver
int[][][] dp = new int[n][2][3]; // cap: 0, 1, 2
for (int[][] a : dp) for (int[] b : a) Arrays.fill(b, -1);
System.out.println(f(0, 1, 2, n, prices, dp));
static int maxProfit(int[] prices) {
int n = prices.length;
int[][][] dp = new int[n + 1][2][3];
// base cases sab 0 hain — Java default ✅
for (int i = n - 1; i >= 0; i--) {
for (int buy = 0; buy <= 1; buy++) {
for (int cap = 1; cap <= 2; cap++) { // cap=0 hamesha 0
int profit;
if (buy == 1) {
profit = Math.max(-prices[i] + dp[i + 1][0][cap],
0 + dp[i + 1][1][cap]);
} else {
profit = Math.max( prices[i] + dp[i + 1][1][cap - 1],
0 + dp[i + 1][0][cap]);
}
dp[i][buy][cap] = profit;
}
}
}
return dp[0][1][2];
}
static int maxProfit(int[] prices) {
int n = prices.length;
int[][] ahead = new int[2][3];
int[][] curr = new int[2][3];
for (int i = n - 1; i >= 0; i--) {
for (int buy = 0; buy <= 1; buy++) {
for (int cap = 1; cap <= 2; cap++) {
if (buy == 1) {
curr[buy][cap] = Math.max(-prices[i] + ahead[0][cap],
0 + ahead[1][cap]);
} else {
curr[buy][cap] = Math.max( prices[i] + ahead[1][cap - 1],
0 + ahead[0][cap]);
}
}
}
// deep copy zaroori hai, warna reference share ho jaayega
for (int b = 0; b < 2; b++)
ahead[b] = curr[b].clone();
}
return ahead[1][2];
}
ahead = curr; likhoge toh dono same array point karenge, aur agli iteration me curr badalte hi ahead bhi badal jaayega. Ye silently galat answer deta hai. clone() ya nayi array banana zaroori hai. (Q7–Q13 me hum har baar new int[] bana rahe the, isliye ye problem nahi aayi thi.)
Dry Run
prices = [3, 3, 5, 0, 0, 3, 1, 4], n = 8
Poori 8×2×3 table lambi hai, toh important cells dekhte hain (ulta chalte hue).
| i | price | [1][2] — buy, 2 left | [0][2] — sell, 2 left | [1][1] — buy, 1 left | [0][1] — sell, 1 left |
|---|---|---|---|---|---|
| 8 | — | 0 | 0 | 0 | 0 |
| 7 | 4 | 0 | 4 | 0 | 4 |
| 6 | 1 | 3 | 4 | 3 | 4 |
| 5 | 3 | 3 | 6 | 3 | 4 |
| 4 | 0 | 6 | 6 | 3 | 4 |
| 3 | 0 | 6 | 6 | 3 | 4 |
| 2 | 5 | 6 | 6 | 3 | 5 |
| 0 | 3 | 6 | — | — | — |
Do important cells samjho
i=5, [0][2] = 6 — din 5 pe share paas me hai, 2 transactions bachi hain.
max(3 + dp[6][1][1], dp[6][0][2]) = max(3 + 3, 4) = 6. Bech diya ₹3 me, aur cap 1 ho gaya — phir bhi ek transaction bachi thi jo aage 3 aur kama layi.
i=4, [1][2] = 6 — din 4 pe khareedne ki baari, price ₹0.
max(-0 + dp[5][0][2], dp[5][1][2]) = max(0 + 6, 3) = 6. Muft me khareed liya ✅
Answer = dp[0][1][2] = 6 ✅
Khud try karo
capko khareedne pe ghatane wala version likho. Base case aur initial call kaise badlenge? (Hint:capab "kitni buy bachi hain" ho jaayega.)ahead[b] = curr[b].clone()koahead = currse badal ke dekho — kya answer galat aata hai?cap = 2ki jagahcap = 1daalo — kya Q35 ka answer aata hai? Aana chahiye.
Stock IV — At Most K Transactions
LeetCode 188 (hard) · Q37 me 2 ki jagah k
Problem
Q37 wahi, bas ab limit 2 nahi k hai.
k = 2, prices = [2, 4, 1] → 2
k = 2, prices = [3, 2, 6, 5, 0, 3] → 7
Soch
Q37 ka code lo, 2 ki jagah k likh do. Bas.
Ye bilkul Q3 → Q4 (Frog Jump → Frog Jump K) wala jump hai — hardcoded constant ko variable bana dena.
| Q37 | Q38 | |
|---|---|---|
| dp size | [n][2][3] | [n][2][k+1] |
| cap loop | 1 to 2 | 1 to k |
| initial call | f(0, 1, 2) | f(0, 1, k) |
| Complexity | O(n × 2 × 3) | O(n × 2 × k) |
Agar k ≥ n/2 ho, toh limit ka koi matlab hi nahi rehta — n dinon me n/2 se zyada transactions ho hi nahi sakti (har transaction ko kam se kam 2 din chahiye). Us case me seedha Q36 (infinite) chala do — O(n) me kaam ho jaayega. Interview me ye optimization mention karna.
Code
static int maxProfit(int k, int[] prices) {
int n = prices.length;
if (n == 0 || k == 0) return 0;
int[][][] dp = new int[n + 1][2][k + 1];
for (int i = n - 1; i >= 0; i--) {
for (int buy = 0; buy <= 1; buy++) {
for (int cap = 1; cap <= k; cap++) {
int profit;
if (buy == 1) {
profit = Math.max(-prices[i] + dp[i + 1][0][cap],
0 + dp[i + 1][1][cap]);
} else {
profit = Math.max( prices[i] + dp[i + 1][1][cap - 1],
0 + dp[i + 1][0][cap]);
}
dp[i][buy][cap] = profit;
}
}
}
return dp[0][1][k];
}
static int maxProfit(int k, int[] prices) {
int n = prices.length;
if (n == 0 || k == 0) return 0;
int[][] ahead = new int[2][k + 1];
for (int i = n - 1; i >= 0; i--) {
int[][] curr = new int[2][k + 1];
for (int buy = 0; buy <= 1; buy++) {
for (int cap = 1; cap <= k; cap++) {
if (buy == 1) {
curr[buy][cap] = Math.max(-prices[i] + ahead[0][cap],
0 + ahead[1][cap]);
} else {
curr[buy][cap] = Math.max( prices[i] + ahead[1][cap - 1],
0 + ahead[0][cap]);
}
}
}
ahead = curr; // curr har baar naya hai, isliye safe
}
return ahead[1][k];
}
Kyunki curr loop ke andar naya banaya jaata hai. Q37 me hum ek hi curr reuse kar rahe the, isliye clone() chahiye tha. Do valid patterns hain — par mix mat karna.
Dry Run
k = 2, prices = [3, 2, 6, 5, 0, 3], n = 6
| i | price | [1][2] | [0][2] | [1][1] | [0][1] |
|---|---|---|---|---|---|
| 6 | — | 0 | 0 | 0 | 0 |
| 5 | 3 | 0 | 3 | 0 | 3 |
| 4 | 0 | 3 | 3 | 3 | 3 |
| 3 | 5 | 3 | 8 | 3 | 5 |
| 2 | 6 | 3 | 9 | 3 | 6 |
| 1 | 2 | 7 | 9 | 4 | 6 |
| 0 | 3 | 7 | — | — | — |
Do cells samjho
i=2, [0][2] = 9 — max(6 + dp[3][1][1], dp[3][0][2]) = max(6 + 3, 8) = 9. ₹6 me becha, aur bachi hui ek transaction se aage 3 aur mile.
i=1, [1][2] = 7 — max(-2 + dp[2][0][2], dp[2][1][2]) = max(-2 + 9, 3) = 7. ₹2 me khareeda ✅
Answer = 7 ✅ — khareedo ₹2 becho ₹6 (4), khareedo ₹0 becho ₹3 (3). Total 7.
Khud try karo
k = 100,priceslength 6 daalo. Kya answer Q36 (infinite) ke barabar aata hai? Aurk ≥ n/2wala shortcut laga ke time compare karo.k = 0→ 0. Guard clause ke bina kya hota? (Array size[n][2][1], cap loop chalta hi nahi,dp[0][1][0] = 0— sahi answer, par guard rakhna saaf hai.)- Q36, Q37, Q38 ka code side-by-side rakho. Q36 = Q38 with
k = ∞, Q37 = Q38 withk = 2, Q35 = Q38 withk = 1. Chaar sawaal, ek code.
Stock with Cooldown
LeetCode 309 · bechne ke baad ek din ka rest
Problem
Infinite transactions, par bechne ke agle din khareed nahi sakte (ek din ka cooldown).
prices = [1, 2, 3, 0, 2] → 3
Khareedo ₹1 → becho ₹2 (profit 1) → cooldown → khareedo ₹0 → becho ₹2 (profit 2). Total 3 ✅
Soch — ek character ka change
Q36 ka poora code lo. Sirf sell wali line me i+1 ko i+2 kar do.
// Q36 (no cooldown)
sell = prices[i] + f(i + 1, 1);
// Q39 (cooldown)
sell = prices[i] + f(i + 2, 1); // ek din skip
Aaj becha (i), kal cooldown (i+1) — toh agla din jab kuch kar sakte ho wo i+2 hai. Bas itna. Kuch aur badalne ki zaroorat nahi. Ye Q5 (House Robber) wali soch hi hai — wahan bhi take karne pe i-2 pe koodte the.
Base case — ab thoda sambhal ke
if (i >= n) return 0; // ⚠️ == nahi, >=
i + 2 ki wajah se index n ko chhod ke seedha n+1 pe pahunch sakta hai. if (i == n) likha toh wo case miss ho jaayega aur ArrayIndexOutOfBounds aayega. >= zaroori hai.
Tabulation me isi wajah se dp array [n + 2][2] banana padta hai, [n + 1][2] nahi.
Code
static int f(int i, int buy, int n, int[] prices, int[][] dp) {
if (i >= n) return 0; // ⚠️ >=
if (dp[i][buy] != -1) return dp[i][buy];
int profit;
if (buy == 1) {
profit = Math.max(-prices[i] + f(i + 1, 0, n, prices, dp),
0 + f(i + 1, 1, n, prices, dp));
} else {
profit = Math.max( prices[i] + f(i + 2, 1, n, prices, dp), // i+2
0 + f(i + 1, 0, n, prices, dp));
}
return dp[i][buy] = profit;
}
static int maxProfit(int[] prices) {
int n = prices.length;
int[][] dp = new int[n + 2][2]; // ⚠️ +2, kyunki i+2 access hota hai
for (int i = n - 1; i >= 0; i--) {
for (int buy = 0; buy <= 1; buy++) {
int profit;
if (buy == 1) {
profit = Math.max(-prices[i] + dp[i + 1][0],
0 + dp[i + 1][1]);
} else {
profit = Math.max( prices[i] + dp[i + 2][1], // i+2
0 + dp[i + 1][0]);
}
dp[i][buy] = profit;
}
}
return dp[0][1];
}
static int maxProfit(int[] prices) {
int n = prices.length;
int[] front2 = new int[2]; // dp[i+2]
int[] front1 = new int[2]; // dp[i+1]
for (int i = n - 1; i >= 0; i--) {
int[] curr = new int[2];
curr[1] = Math.max(-prices[i] + front1[0], front1[1]);
curr[0] = Math.max( prices[i] + front2[1], front1[0]);
front2 = front1; // window slide, do kadam
front1 = curr;
}
return front1[1];
}
Ab tak Phase 5 me ahead aur curr se kaam chal raha tha. Yahan i+2 access hota hai, toh do aage wali rows rakhni padti hain. Bilkul Q1 (Fibonacci) ke prev/prev2 jaisa — dependency jitni door tak jaati hai, utni rows rakho.
Dry Run
prices = [1, 2, 3, 0, 2], n = 5. Ulta chalte hain.
| i | price | front2[1] | front1[0] | front1[1] | curr[1] = max(−p+f1[0], f1[1]) | curr[0] = max(p+f2[1], f1[0]) |
|---|---|---|---|---|---|---|
| 5,6 | — | 0 | 0 | 0 | — | — |
| 4 | 2 | 0 | 0 | 0 | max(−2+0, 0) = 0 | max(2+0, 0) = 2 |
| 3 | 0 | 0 | 2 | 0 | max(−0+2, 0) = 2 | max(0+0, 2) = 2 |
| 2 | 3 | 0 | 2 | 2 | max(−3+2, 2) = 2 | max(3+0, 2) = 3 |
| 1 | 2 | 2 | 3 | 2 | max(−2+3, 2) = 2 | max(2+2, 3) = 4 |
| 0 | 1 | 2 | 4 | 2 | max(−1+4, 2) = 3 | max(1+2, 4) = 4 |
Answer = 3 ✅
Cooldown kahan dikha
i=1 pe curr[0] = 4 aaya 2 + front2[1] se — matlab din 1 pe becha, aur agla khareedne ka mauka din 3 pe (din 2 cooldown). Aur front2[1] tab dp[3][1] = 2 tha ✅
Agar cooldown na hota (Q36), toh i=1 pe 2 + dp[2][1] = 2 + 2 = 4 hi aata — is example me sanyog se same. Par bade arrays me farak saaf dikhta hai.
Khud try karo
i+2koi+1kar do — Q36 ban jaana chahiye. Same input pe answer 4 aayega (cooldown ke bina behtar).- dp array
[n+1][2]banao (+2ki jagah) — crash hota hai? Kaunse input pe? - Cooldown
2din ka ho toh? (i+3, aurfront3bhi rakhna padega.)
Stock with Transaction Fee
LeetCode 714 · Phase 5 ka aakhri — sabse aasan
Problem
Infinite transactions, par har poori transaction pe ek fixed fee lagti hai. Maximum profit?
prices = [1, 3, 2, 8, 4, 9], fee = 2 → 8
Khareedo ₹1 becho ₹8 → 8 - 1 - 2 = 5. Khareedo ₹4 becho ₹9 → 9 - 4 - 2 = 3. Total 8 ✅
Soch — ek term ka change
Q36 ka code lo. Sell wali line me - fee jod do.
// Q36
sell = prices[i] + f(i + 1, 1);
// Q40
sell = prices[i] - fee + f(i + 1, 1);
Dono chalte hain! -prices[i] - fee (buy pe) ya +prices[i] - fee (sell pe) — ek transaction me ek hi baar lagti hai, toh answer same.
Par sell pe lagana behtar hai, kyunki tab fee sirf poori transactions pe lagti hai. Buy pe lagate toh aakhri me agar share bina bike reh gaya (jo optimal solution me hota nahi, par logically) toh fee bekaar lag jaati.
Code
static int f(int i, int buy, int n, int fee, int[] prices, int[][] dp) {
if (i == n) return 0;
if (dp[i][buy] != -1) return dp[i][buy];
int profit;
if (buy == 1) {
profit = Math.max(-prices[i] + f(i + 1, 0, n, fee, prices, dp),
0 + f(i + 1, 1, n, fee, prices, dp));
} else {
profit = Math.max( prices[i] - fee + f(i + 1, 1, n, fee, prices, dp),
0 + f(i + 1, 0, n, fee, prices, dp));
}
return dp[i][buy] = profit;
}
static int maxProfit(int[] prices, int fee) {
int n = prices.length;
int aheadBuy = 0, aheadSell = 0;
for (int i = n - 1; i >= 0; i--) {
int currBuy = Math.max(-prices[i] + aheadSell, aheadBuy);
int currSell = Math.max( prices[i] - fee + aheadBuy, aheadSell);
aheadBuy = currBuy;
aheadSell = currSell;
}
return aheadBuy;
}
Dry Run
prices = [1, 3, 2, 8, 4, 9], fee = 2, n = 6
| i | price | currBuy = max(−p + aheadSell, aheadBuy) | currSell = max(p − 2 + aheadBuy, aheadSell) |
|---|---|---|---|
| 6 | — | 0 | 0 |
| 5 | 9 | max(−9+0, 0) = 0 | max(9−2+0, 0) = 7 |
| 4 | 4 | max(−4+7, 0) = 3 | max(4−2+0, 7) = 7 |
| 3 | 8 | max(−8+7, 3) = 3 | max(8−2+3, 7) = 9 |
| 2 | 2 | max(−2+9, 3) = 7 | max(2−2+3, 9) = 9 |
| 1 | 3 | max(−3+9, 7) = 7 | max(3−2+7, 9) = 9 |
| 0 | 1 | max(−1+9, 7) = 8 | max(1−2+8, 9) = 9 |
Answer = 8 ✅
Fee ka asar dekho
i=4 pe currSell = 7 aaya — aur wo aheadSell (7) se aaya, na ki 4 - 2 + 0 = 2 se. Matlab din 4 pe ₹4 me bechna faayde ka nahi tha — fee kha jaati. Ruk ke din 5 pe ₹9 me bechna behtar raha.
Bina fee ke (Q36) same input pe answer 13 aata (2+6+5). Fee ne 5 kha liya — aur chhoti transactions ko poori tarah band kar diya.
🎉 Phase 5 complete
| # | Sawaal | State | Q36 se farak |
|---|---|---|---|
| 35 | Stock I (1 txn) | minPrice | cap = 1 |
| 36 | Stock II (infinite) | i, buy | baseline |
| 37 | Stock III (2 txn) | i, buy, cap | cap add |
| 38 | Stock IV (k txn) | i, buy, cap | cap = k |
| 39 | Cooldown | i, buy | sell → i+2 |
| 40 | Transaction Fee | i, buy | sell → −fee |
Chhe sawaal, ek recurrence. Q36 ka code likh lo aur teen knobs yaad rakho: cap add karo (Q37/38), sell pe i+2 (Q39), sell pe -fee (Q40). Poora Phase 5 khatam.
Khud try karo
fee = 0daalo → Q36 ka answer (13) aana chahiye.- Fee ko buy wali line me shift karo — same answer aata hai? (Haan.)
- Cooldown + fee dono wala version likho. Kaunsi do lines badalni padengi? (Dono sell wali line me —
prices[i] - fee + dp[i+2][1].)
Phase 6 · Q41–Q47
LIS — Longest Increasing Subsequence
Ek naya dhaancha: dp[i] ka matlab hai "index i pe khatam hone wali best subsequence". Aur answer poore array ka maximum hota hai, aakhri cell nahi — bilkul Q27 (Common Substring) jaisa. Saat sawaal, aur chaar me sirf ek condition badalti hai.
dp[i] = 1 // akela element bhi ek subsequence hai
for j = 0 to i-1:
if (CONDITION(arr[j], arr[i]))
dp[i] = max(dp[i], 1 + dp[j])
answer = max(dp[0..n-1]) // aakhri cell NAHI
Longest Increasing Subsequence
LeetCode 300 · Phase 6 ki neev
Problem
Array me sabse lambi strictly increasing subsequence ki length.
nums = [10, 9, 2, 5, 3, 7, 101, 18] → 4 ([2, 3, 7, 101] ya [2, 3, 7, 18])
Soch 1 — take/notTake with prev index
Phase 3 wali soch: har element pe "loon ya na loon?". Par yahan ek naya sawaal bhi hai — "pichla kaunsa liya tha?", kyunki increasing rakhna hai.
Toh state = (index, prevIndex):
f(i, prev):
if (i == n) return 0;
notTake = 0 + f(i+1, prev);
take = 0;
if (prev == -1 || arr[i] > arr[prev])
take = 1 + f(i+1, i); // ab prev = i
return max(take, notTake);
prev ki value -1 se n-1 tak jaati hai — aur dp[-1] possible nahi. Toh coordinate shift karna padta hai: dp[i][prev + 1], array size [n][n+1]. Ye kaam karta hai par ganda hai, aur O(n²) space leta hai.
Soch 2 — "i pe khatam" wala dhaancha (yahi seekho)
dp[i] = "index i pe khatam hone wali sabse lambi increasing subsequence ki length"
Iska faayda: state sirf ek parameter ka hai (i), aur prev ki zaroorat hi nahi — kyunki "i pe khatam" ka matlab hi hai ki arr[i] aakhri element hai.
dp[i] = 1 + max( dp[j] ) for all j < i where arr[j] < arr[i]
Aur agar koi bhi aisa j nahi mila, toh dp[i] = 1 (akela element).
Answer aakhri cell me kyun nahi hai
Kyunki sabse lambi subsequence kisi bhi index pe khatam ho sakti hai. Example me dp[7] = 4 hai (18 pe khatam) aur dp[6] = 4 bhi (101 pe). Par agar array [1,2,3,0] hota toh dp[3] = 1 hota aur answer dp[2] = 3 hota. Poori array scan karo.
Code
static int lengthOfLIS(int[] nums) {
int n = nums.length;
int[] dp = new int[n];
Arrays.fill(dp, 1); // har element akela = length 1
int ans = 1;
for (int i = 0; i < n; i++) {
for (int j = 0; j < i; j++) {
if (nums[j] < nums[i]) { // CONDITION
dp[i] = Math.max(dp[i], 1 + dp[j]);
}
}
ans = Math.max(ans, dp[i]); // har i pe answer update
}
return ans;
}
static int f(int i, int prev, int n, int[] nums, int[][] dp) {
if (i == n) return 0;
if (dp[i][prev + 1] != -1) return dp[i][prev + 1]; // shift
int notTake = 0 + f(i + 1, prev, n, nums, dp);
int take = 0;
if (prev == -1 || nums[i] > nums[prev]) {
take = 1 + f(i + 1, i, n, nums, dp);
}
return dp[i][prev + 1] = Math.max(take, notTake);
}
// call: f(0, -1, n, nums, dp) with dp = new int[n][n+1]
Version 1 — chhota, saaf, O(n) space, aur Q42–Q47 sab isi pe bante hain. Version 2 sirf tab likho jab interviewer specifically "recursion se derive karo" bole.
Dry Run
nums = [10, 9, 2, 5, 3, 7, 101, 18]
| i | nums[i] | Kaunse j chale (nums[j] < nums[i]) | 1 + max(dp[j]) | dp[i] |
|---|---|---|---|---|
| 0 | 10 | — | — | 1 |
| 1 | 9 | koi nahi (10 > 9) | — | 1 |
| 2 | 2 | koi nahi | — | 1 |
| 3 | 5 | j=2 (2<5), dp[2]=1 | 1+1 | 2 |
| 4 | 3 | j=2 (2<3), dp[2]=1 | 1+1 | 2 |
| 5 | 7 | j=2 (dp=1), j=3 (dp=2), j=4 (dp=2) | 1+2 | 3 |
| 6 | 101 | j=0..5 sab, max dp = 3 (j=5) | 1+3 | 4 |
| 7 | 18 | j=0..5 (101 nahi), max dp = 3 (j=5) | 1+3 | 4 |
| index | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|---|---|---|---|---|---|---|---|---|
| nums | 10 | 9 | 2 | 5 | 3 | 7 | 101 | 18 |
| dp | 1 | 1 | 1 | 2 | 2 | 3 | 4 | 4 |
Answer = max(dp) = 4 ✅
Table ko padho
dp[0] = dp[1] = dp[2] = 1— shuruaat me values girti ja rahi hain (10, 9, 2), toh koi chain banti hi nahi.dp[5] = 3—[2, 3, 7]ya[2, 5, 7]. Dono length 3.dp[6] = dp[7] = 4— do alag subsequences, dono length 4. DP ko farak nahi padta.- Aakhri cell
dp[7] = 4ittefaq se answer hai.nums = [10, 9, 2, 5, 3, 7, 101, 1]hota tohdp[7] = 1hota par answer phir bhi 4.
Complexity
| Approach | Time | Space |
|---|---|---|
| Memoization (i, prev) | O(n²) | O(n²) + O(n) |
| Tabulation (i pe khatam) | O(n²) | O(n) |
| Binary search (Q43) | O(n log n) | O(n) |
Khud try karo
nums = [7,7,7,7]→ 1. Condition<hai,≤nahi — strictly increasing.- Condition ko
≤kar do → non-decreasing LIS milegi. Same input pe ab 4 aayega. - Longest Decreasing Subsequence — condition
nums[j] > nums[i]kar do. Bas.
Print the LIS
GFG · hash array se backtrack
Problem
Sirf length nahi, actual subsequence print karo.
nums = [10, 9, 2, 5, 3, 7, 101, 18] → [2, 3, 7, 18]
Soch — parent pointer rakho
Q41 me jab dp[i] update hota hai, hum bhool jaate hain ki kaunse j se aaya tha. Bas wahi yaad rakh lo.
| Array | Matlab |
|---|---|
dp[i] | i pe khatam hone wali LIS ki length |
hash[i] | kaunse index se aaya tha (parent). Agar koi nahi, toh i khud. |
Phir lastIndex (jahan max mila) se shuru karke hash follow karte jao jab tak hash[i] == i na ho jaye. Bilkul linked list traverse karne jaisa.
Code
static ArrayList<Integer> printLIS(int[] nums) {
int n = nums.length;
int[] dp = new int[n];
int[] hash = new int[n];
Arrays.fill(dp, 1);
int maxLen = 1, lastIndex = 0;
for (int i = 0; i < n; i++) {
hash[i] = i; // default: khud parent
for (int j = 0; j < i; j++) {
if (nums[j] < nums[i] && 1 + dp[j] > dp[i]) {
dp[i] = 1 + dp[j];
hash[i] = j; // parent yaad rakho
}
}
if (dp[i] > maxLen) {
maxLen = dp[i];
lastIndex = i; // answer kahan khatam hua
}
}
// ---- backtrack ----
ArrayList<Integer> lis = new ArrayList<>();
lis.add(nums[lastIndex]);
while (hash[lastIndex] != lastIndex) {
lastIndex = hash[lastIndex];
lis.add(nums[lastIndex]);
}
Collections.reverse(lis); // ulta bana tha
return lis;
}
1. Condition me 1 + dp[j] > dp[i] hona chahiye (≥ nahi). Warna barabar length pe bhi hash update hoga aur galat parent set ho jaayega.
2. hash[i] = i loop ke shuru me set karo, warna kuch indices ka hash 0 reh jaayega (Java default) aur backtrack galat chalega.
Dry Run
nums = [10, 9, 2, 5, 3, 7, 101, 18]
| index | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|---|---|---|---|---|---|---|---|---|
| nums | 10 | 9 | 2 | 5 | 3 | 7 | 101 | 18 |
| dp | 1 | 1 | 1 | 2 | 2 | 3 | 4 | 4 |
| hash | 0 | 1 | 2 | 2 | 2 | 3 | 5 | 5 |
maxLen = 4, lastIndex = 6 (pehla jahan 4 mila — condition > hai, isliye index 7 se update nahi hua)
Backtrack trace
| Step | lastIndex | nums[lastIndex] | hash[lastIndex] | lis (ulta) |
|---|---|---|---|---|
| 1 | 6 | 101 | 5 | [101] |
| 2 | 5 | 7 | 3 | [101, 7] |
| 3 | 3 | 5 | 2 | [101, 7, 5] |
| 4 | 2 | 2 | 2 (== khud) | [101, 7, 5, 2] |
reverse() → [2, 5, 7, 101] ✅
Length 4 ✅. Ye [2, 3, 7, 101] se alag hai par dono valid hain — hash[3] = 2 tha kyunki j=2 pehle mila tha. Multiple LIS ho toh koi bhi ek acceptable hai.
Khud try karo
- Condition ko
1 + dp[j] ≥ dp[i]kar do — kya output galat aata hai? Kaunse input pe? lastIndextrack karne ki jagah end medparray scan karo — same result aana chahiye.- Saari LIS print karo (sirf ek nahi). Recursion + backtracking chahiye hoga.
LIS in O(n log n)
LeetCode 300 (optimal) · ye DP nahi hai — aur wahi seekhne wali baat hai
Soch — ek greedy idea
Ek temp list rakho. Har naye element x ke liye:
- Agar
xtempke aakhri element se bada hai → end meappendkaro (chain lambi ho gayi) - Warna →
tempme sabse chhoti aisi value dhoondo joxse≥hai, aur uskoxse replace karo
Answer = temp.size()
temp hamesha sorted rehti hai. Chhoti value se replace karne ka matlab: "utni hi lambi chain, par ab kam value pe khatam ho rahi hai" — jo aage aur elements jodne ke liye behtar hai. Chain ki length nahi badalti, sirf uska "potential" behtar hota hai.
temp ki length hamesha sahi hoti hai, par temp ke contents aksar actual LIS nahi hote! Replace hone ki wajah se andar mila-jula ho jaata hai. Agar actual subsequence chahiye toh Q42 (O(n²) + hash) hi use karo.
Code
static int lengthOfLIS(int[] nums) {
ArrayList<Integer> temp = new ArrayList<>();
temp.add(nums[0]);
for (int i = 1; i < nums.length; i++) {
if (nums[i] > temp.get(temp.size() - 1)) {
temp.add(nums[i]); // chain lambi
} else {
// pehla index jahan temp[idx] ≥ nums[i]
int idx = lowerBound(temp, nums[i]);
temp.set(idx, nums[i]); // replace
}
}
return temp.size();
}
// lower_bound: pehla index jahan list.get(idx) ≥ target
static int lowerBound(ArrayList<Integer> list, int target) {
int lo = 0, hi = list.size() - 1, ans = list.size();
while (lo <= hi) {
int mid = lo + (hi - lo) / 2;
if (list.get(mid) >= target) {
ans = mid;
hi = mid - 1;
} else {
lo = mid + 1;
}
}
return ans;
}
Arrays.binarySearch() use kar sakte ho, par wo not-found pe -(insertionPoint) - 1 return karta hai — usko decode karna padta hai. Interview me apna lowerBound likhna zyada saaf hai aur off-by-one bugs kam hote hain.
Dry Run
nums = [10, 9, 2, 5, 3, 7, 101, 18]
| i | nums[i] | temp (pehle) | Action | temp (baad me) |
|---|---|---|---|---|
| 0 | 10 | [] | init | [10] |
| 1 | 9 | [10] | 9 < 10 → replace idx 0 | [9] |
| 2 | 2 | [9] | 2 < 9 → replace idx 0 | [2] |
| 3 | 5 | [2] | 5 > 2 → append | [2, 5] |
| 4 | 3 | [2, 5] | 3 < 5 → replace idx 1 | [2, 3] |
| 5 | 7 | [2, 3] | 7 > 3 → append | [2, 3, 7] |
| 6 | 101 | [2, 3, 7] | 101 > 7 → append | [2, 3, 7, 101] |
| 7 | 18 | [2, 3, 7, 101] | 18 < 101 → replace idx 3 | [2, 3, 7, 18] |
Answer = temp.size() = 4 ✅
Yahan temp sanyog se sahi LIS hai
[2, 3, 7, 18] sach me ek valid LIS hai. Par ye hamesha nahi hota. Dekho:
| nums | final temp | size | Kya temp valid LIS hai? |
|---|---|---|---|
| [1, 2, 3] | [1, 2, 3] | 3 | haan |
| [3, 4, 5, 1] | [1, 4, 5] | 3 | nahi! (1 aakhir me tha) |
[3, 4, 5, 1] me answer 3 sahi hai ([3,4,5]), par temp = [1, 4, 5] me 1 aakhri element tha — wo 4 aur 5 se pehle aa hi nahi sakta. Length sahi, contents galat. Yahi wo caveat hai.
Complexity
n elements, har ek pe O(log n) binary search → O(n log n). Space O(n).
Khud try karo
[3, 4, 5, 1]chala ketempprint karo — upar wala caveat khud dekho.lowerBoundkoupperBound(pehla>target) me badal do → non-decreasing LIS milegi.[7,7,7]pe test karo:1vs3.- Q41 (O(n²)) aur Q43 dono ko 10⁵ size ke random array pe chalao — time ka farak khud dekho.
Largest Divisible Subset
LeetCode 368 · LIS with a different condition
Problem
Sabse bada subset dhoondo jisme har jodi (a, b) ke liye ya toh a % b == 0 ho ya b % a == 0.
nums = [1, 16, 7, 8, 4] → [1, 4, 8, 16] (size 4)
Soch — do insights
1 · Sort karna zaroori hai
Sawaal subset maangta hai — order matter nahi karta. Toh hum array ko sort kar sakte hain, aur usse problem "subsequence" ban jaati hai — jise hum LIS se solve kar sakte hain.
Ye Phase 3 ke Q15 wali "problem reduction" hi hai, bas ek sort ke saath.
2 · Sorted me sirf padosi check karna kaafi hai
Ye subtle hai. Agar list sorted hai aur har element apne pichle element se divisible hai, toh saare pairs automatically divisible ho jaate hain.
Kyunki divisibility transitive hai: 8 % 4 == 0 aur 4 % 1 == 0 → toh 8 % 1 == 0 bhi. Isliye chain me har consecutive pair check karna kaafi hai, saare pairs nahi.
Toh code Q42 ka hi hai, sirf:
Arrays.sort(nums); // ⚠️ naya
if (nums[i] % nums[j] == 0 && ...) // condition badli
// Q41 me ye tha: if (nums[j] < nums[i] && ...)
Code
static List<Integer> largestDivisibleSubset(int[] nums) {
int n = nums.length;
Arrays.sort(nums); // ⚠️ pehla step
int[] dp = new int[n];
int[] hash = new int[n];
Arrays.fill(dp, 1);
int maxLen = 1, lastIndex = 0;
for (int i = 0; i < n; i++) {
hash[i] = i;
for (int j = 0; j < i; j++) {
if (nums[i] % nums[j] == 0 && 1 + dp[j] > dp[i]) {
dp[i] = 1 + dp[j];
hash[i] = j;
}
}
if (dp[i] > maxLen) {
maxLen = dp[i];
lastIndex = i;
}
}
// backtrack — Q42 jaisa
List<Integer> ans = new ArrayList<>();
ans.add(nums[lastIndex]);
while (hash[lastIndex] != lastIndex) {
lastIndex = hash[lastIndex];
ans.add(nums[lastIndex]);
}
Collections.reverse(ans);
return ans;
}
Dry Run
nums = [1, 16, 7, 8, 4] → sort → [1, 4, 7, 8, 16]
| i | nums[i] | Kaunse j pe divisible | dp[i] | hash[i] |
|---|---|---|---|---|
| 0 | 1 | — | 1 | 0 |
| 1 | 4 | j=0 (4%1=0), dp[0]=1 | 2 | 0 |
| 2 | 7 | j=0 (7%1=0), dp[0]=1 | 2 | 0 |
| 3 | 8 | j=0 (dp=1), j=1 (8%4=0, dp=2) | 3 | 1 |
| 4 | 16 | j=0 (dp=1), j=1 (16%4=0, dp=2), j=3 (16%8=0, dp=3) | 4 | 3 |
| index | 0 | 1 | 2 | 3 | 4 |
|---|---|---|---|---|---|
| nums (sorted) | 1 | 4 | 7 | 8 | 16 |
| dp | 1 | 2 | 2 | 3 | 4 |
| hash | 0 | 0 | 0 | 1 | 3 |
maxLen = 4, lastIndex = 4
Backtrack
4 → hash[4]=3 → hash[3]=1 → hash[1]=0 → hash[0]=0 (ruk gaya)
Values: 16, 8, 4, 1 → reverse → [1, 4, 8, 16] ✅
Verify — saare pairs
| Pair | Divisible? |
|---|---|
| 4 % 1 | 0 ✅ |
| 8 % 1 | 0 ✅ |
| 8 % 4 | 0 ✅ |
| 16 % 1, 16 % 4, 16 % 8 | sab 0 ✅ |
Chhe pairs, sab divisible — aur humne code me sirf consecutive check kiye the. Transitivity ne baaki sambhal liya. 🎯
Khud try karo
Arrays.sort()hata do —[1, 16, 7, 8, 4]pe kya aata hai? (Chhota answer, kyunki4aakhir me hai aur16use dekh hi nahi paata.)nums = [1, 2, 3]→[1, 2]ya[1, 3], size 2.- Q42 ka code aur Q44 ka code side-by-side rakho — do line ka farak hai (sort + condition). Wahi Phase 6 ka poora sabak hai.
Longest String Chain
LeetCode 1048 · LIS with a custom predicate
Problem
Words ki list hai. wordA wordB ka predecessor hai agar wordA me exactly ek character kahin bhi insert karke wordB ban jaye.
Sabse lambi chain ki length nikalo.
words = ["a","b","ba","bca","bda","bdca"] → 4 ("a" → "ba" → "bda" → "bdca")
Soch
1 · Length ke hisaab se sort karo
Chain me har agla word exactly ek character bada hota hai. Toh agar length se sort kar dein, chain hamesha left-to-right chalegi — aur ye LIS ban jaayegi.
Arrays.sort(words, (a, b) -> a.length() - b.length());
2 · Condition ek helper function ban gayi
Q41 me condition nums[j] < nums[i] thi — ek comparison. Yahan wo ek function hai:
isPredecessor(s1, s2):
// kya s1 me ek char daal ke s2 ban sakta hai?
if (s2.length() != s1.length() + 1) return false;
two pointers se check karo
Q41 → Q44 → Q45: sirf condition badalti hai. Baaki dp loop, 1 + dp[j], max scan — sab identical. Condition chahe < ho, % ho, ya poora function — dhaancha wahi.
isPredecessor kaise likhein
Do pointer chalao. Jab mismatch mile, ek baar lambi string ka pointer aage badhao (wahi insert kiya hua character hai). Doosri baar mismatch mila toh false.
Code
static boolean isPredecessor(String s1, String s2) {
// s1 chhota, s2 bada — exactly 1 char ka farak
if (s2.length() != s1.length() + 1) return false;
int i = 0, j = 0;
boolean skipped = false;
while (i < s1.length() && j < s2.length()) {
if (s1.charAt(i) == s2.charAt(j)) {
i++; j++;
} else {
if (skipped) return false; // doosra mismatch → nahi
skipped = true;
j++; // bade wale ka char skip
}
}
return true;
}
static int longestStrChain(String[] words) {
int n = words.length;
Arrays.sort(words, (a, b) -> a.length() - b.length());
int[] dp = new int[n];
Arrays.fill(dp, 1);
int ans = 1;
for (int i = 0; i < n; i++) {
for (int j = 0; j < i; j++) {
if (isPredecessor(words[j], words[i]) && 1 + dp[j] > dp[i]) {
dp[i] = 1 + dp[j];
}
}
ans = Math.max(ans, dp[i]);
}
return ans;
}
while loop ke baad seedha return true kar diya. Kyun safe hai? Kyunki length ka check pehle hi ho chuka hai (s2 = s1 + 1), toh i ke khatam hone pe j me zyada se zyada ek character bacha hoga — aur wo hi insert kiya hua hai.
Dry Run
words = ["a","b","ba","bca","bda","bdca"] — already length-sorted
| i | word | Kaunse j predecessor hain | dp[i] |
|---|---|---|---|
| 0 | "a" | — | 1 |
| 1 | "b" | koi nahi (same length) | 1 |
| 2 | "ba" | j=0 ("a"→"ba" ✅), j=1 ("b"→"ba" ✅) | 2 |
| 3 | "bca" | j=2 ("ba"→"bca" ✅), dp[2]=2 | 3 |
| 4 | "bda" | j=2 ("ba"→"bda" ✅), dp[2]=2 | 3 |
| 5 | "bdca" | j=3 ("bca"→"bdca" ✅ dp=3), j=4 ("bda"→"bdca" ✅ dp=3) | 4 |
| word | a | b | ba | bca | bda | bdca |
|---|---|---|---|---|---|---|
| dp | 1 | 1 | 2 | 3 | 3 | 4 |
Answer = 4 ✅ — chain: "a" → "ba" → "bda" → "bdca"
isPredecessor("ba", "bda") ka trace
| i, j | s1[i] | s2[j] | Match? | Action |
|---|---|---|---|---|
| 0, 0 | b | b | ✅ | i→1, j→1 |
| 1, 1 | a | d | ❌ | skipped=true, j→2 |
| 1, 2 | a | a | ✅ | i→2, j→3 |
| 2, 3 | i == s1.length() → loop khatam | return true ✅ | ||
Complexity
O(n log n) sort + O(n²) dp loops × O(L) per isPredecessor = O(n² × L), jahan L = max word length.
HashMap se bhi hota hai: har word ke liye uske saare possible predecessors (ek char hata ke) generate karo aur map me dhoondo. O(n × L²) — bade n pe zyada tez. Par interview me LIS version likhna kaafi hai, aur pattern recognition dikhata hai.
Khud try karo
- Sort hata do —
["bdca","bda","ba","a"]pe kya aata hai? (1, kyunki chain ulti direction me hai.) - Actual chain print karo — Q42 wala
hash[]laga do. - HashMap wala
O(n × L²)version likho aur dono ka time compare karo.
Longest Bitonic Subsequence
GFG · do LIS, ulti disha me
Problem
Bitonic subsequence = pehle strictly badhti hai, phir strictly ghatti hai. (Sirf badhti ya sirf ghatti bhi valid hai.)
nums = [1, 2, 1, 2, 1] → 3 ([1, 2, 1])
nums = [1, 11, 2, 10, 4, 5, 2, 1] → 6 ([1, 2, 10, 4, 2, 1])
Soch — peak ko fix karo
Har bitonic subsequence ka ek peak hota hai. Toh har index i ko peak maan ke dekho:
bitonic(i) = (i pe khatam hone wali LIS) + (i se shuru hone wali LDS) − 1
−1 kyun? Kyunki peak dono me count ho jaata hai. Ek baar ghata do.
| Array | Matlab | Kaise bhare |
|---|---|---|
dp1[i] | i pe khatam hone wali LIS (badhti hui) | Q41 seedha, left → right |
dp2[i] | i se shuru hone wali LDS (ghatti hui) | Q41 ulta, right → left |
"i se shuru hone wali decreasing" = "array ko ulta karke i pe khatam hone wali increasing". Isliye same Q41 code, bas dono loops ulti direction me.
Code
static int longestBitonicSequence(int[] nums, int n) {
int[] dp1 = new int[n]; // i pe khatam, badhti hui
int[] dp2 = new int[n]; // i se shuru, ghatti hui
Arrays.fill(dp1, 1);
Arrays.fill(dp2, 1);
// ---- LIS left to right ----
for (int i = 0; i < n; i++) {
for (int j = 0; j < i; j++) {
if (nums[j] < nums[i]) {
dp1[i] = Math.max(dp1[i], 1 + dp1[j]);
}
}
}
// ---- LIS right to left (= LDS) ----
for (int i = n - 1; i >= 0; i--) {
for (int j = n - 1; j > i; j--) {
if (nums[j] < nums[i]) {
dp2[i] = Math.max(dp2[i], 1 + dp2[j]);
}
}
}
// ---- har index ko peak maan ke dekho ----
int ans = 0;
for (int i = 0; i < n; i++) {
ans = Math.max(ans, dp1[i] + dp2[i] - 1);
}
return ans;
}
Dono jagah nums[j] < nums[i] hai — ye galti nahi hai. Doosre loop me j > i hai (right side), toh nums[j] < nums[i] ka matlab banta hai "i ke baad wali value chhoti hai" = ghatti hui. Direction ne condition ka matlab palat diya.
Dry Run
nums = [1, 11, 2, 10, 4, 5, 2, 1], n = 8
dp1 — LIS left to right
| index | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|---|---|---|---|---|---|---|---|---|
| nums | 1 | 11 | 2 | 10 | 4 | 5 | 2 | 1 |
| dp1 | 1 | 2 | 2 | 3 | 3 | 4 | 2 | 1 |
dp1[3] = 3 → [1, 2, 10] · dp1[5] = 4 → [1, 2, 4, 5]
dp2 — LDS right to left
| index | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|---|---|---|---|---|---|---|---|---|
| nums | 1 | 11 | 2 | 10 | 4 | 5 | 2 | 1 |
| dp2 | 1 | 5 | 3 | 4 | 3 | 3 | 2 | 1 |
dp2[3] = 4 → [10, 4, 2, 1] · dp2[1] = 5 → [11, 10, 4, 2, 1]
Combine — har index peak
| i | nums[i] | dp1[i] | dp2[i] | sum − 1 |
|---|---|---|---|---|
| 0 | 1 | 1 | 1 | 1 |
| 1 | 11 | 2 | 5 | 6 |
| 2 | 2 | 2 | 3 | 4 |
| 3 | 10 | 3 | 4 | 6 |
| 4 | 4 | 3 | 3 | 5 |
| 5 | 5 | 4 | 3 | 6 |
| 6 | 2 | 2 | 2 | 3 |
| 7 | 1 | 1 | 1 | 1 |
Answer = 6 ✅
Teen alag peaks, same answer
| Peak | Subsequence |
|---|---|
| i=1 (11) | [1, 11] + [11, 10, 4, 2, 1] = [1, 11, 10, 4, 2, 1] |
| i=3 (10) | [1, 2, 10] + [10, 4, 2, 1] = [1, 2, 10, 4, 2, 1] |
| i=5 (5) | [1, 2, 4, 5] + [5, 2, 1] = [1, 2, 4, 5, 2, 1] |
Teeno length 6 hain. DP ko farak nahi padta — wo bas maximum batata hai.
Khud try karo
nums = [1, 2, 3, 4](sirf badhti) → 4.dp2sab1hoga.−1hata do — answer 7 aayega. Peak do baar gina gaya.- Kuch definitions me bitonic ke liye dono hisse zaroori hote hain (sirf increasing valid nahi). Us case me condition kya lagegi? (
dp1[i] > 1 && dp2[i] > 1)
Number of Longest Increasing Subsequences
LeetCode 673 · Phase 6 ka finale — do arrays chahiye
Problem
Kitni longest increasing subsequences hain? (Length nahi, ginti.)
nums = [1, 3, 5, 4, 7] → 2 ([1,3,5,7] aur [1,3,4,7])
nums = [2, 2, 2, 2, 2] → 5 (LIS length 1, aur paanch alag single elements)
Soch — do arrays saath-saath
| Array | Matlab |
|---|---|
dp[i] | i pe khatam hone wali LIS ki length |
cnt[i] | utni hi length wali kitni subsequences hain jo i pe khatam hoti hain |
Do cases — yahi poora sawaal hai
Jab nums[j] < nums[i] ho:
1 + dp[j] > dp[i]
Purani length beat ho gayi. Toh length update karo, aur count ko reset karo (purani counts ab bekaar hain):
dp[i] = 1 + dp[j];
cnt[i] = cnt[j]; // reset, += nahi
1 + dp[j] == dp[i]
Utni hi lambi chain, par alag raaste se. Toh length wahi rehti hai, aur count jud jaata hai:
cnt[i] += cnt[j]; // = nahi, +=
> pe = lagta hai aur == pe +=. Ulta kar diya toh counts galat aayenge, par length sahi aayegi — toh bug pakadna mushkil hota hai. Yaad rakho: nayi length → reset. Barabar length → add.
Final answer
maxLen nikalo, phir saare i ka cnt[i] jodo jahan dp[i] == maxLen. Ek hi index kaafi nahi — multiple indices pe LIS khatam ho sakti hai.
Code
static int findNumberOfLIS(int[] nums) {
int n = nums.length;
int[] dp = new int[n];
int[] cnt = new int[n];
Arrays.fill(dp, 1);
Arrays.fill(cnt, 1); // har element akela = 1 tareeka
int maxLen = 1;
for (int i = 0; i < n; i++) {
for (int j = 0; j < i; j++) {
if (nums[j] < nums[i]) {
if (1 + dp[j] > dp[i]) { // Case A
dp[i] = 1 + dp[j];
cnt[i] = cnt[j]; // reset
}
else if (1 + dp[j] == dp[i]) { // Case B
cnt[i] += cnt[j]; // add
}
}
}
maxLen = Math.max(maxLen, dp[i]);
}
// saare indices jahan maxLen khatam hui
int total = 0;
for (int i = 0; i < n; i++) {
if (dp[i] == maxLen) total += cnt[i];
}
return total;
}
Dry Run
nums = [1, 3, 5, 4, 7]
| i | nums[i] | j | Case | dp[i] | cnt[i] |
|---|---|---|---|---|---|
| 0 | 1 | — | — | 1 | 1 |
| 1 | 3 | j=0: 1+1 > 1 ✅ | A | 2 | 1 |
| 2 | 5 | j=0: 1+1 > 1 ✅ | A | 2 | 1 |
| j=1: 1+2 > 2 ✅ | A | 3 | 1 | ||
| 3 | 4 | j=0: 1+1 > 1 ✅ | A | 2 | 1 |
| j=1: 1+2 > 2 ✅ | A | 3 | 1 | ||
| j=2: 5 > 4, skip | — | 3 | 1 | ||
| 4 | 7 | j=0: 1+1 > 1 ✅ | A | 2 | 1 |
| j=1: 1+2 > 2 ✅ | A | 3 | 1 | ||
| j=2: 1+3 > 3 ✅ | A | 4 | 1 | ||
| j=3: 1+3 == 4 ✅ | B | 4 | 2 |
| index | 0 | 1 | 2 | 3 | 4 |
|---|---|---|---|---|---|
| nums | 1 | 3 | 5 | 4 | 7 |
| dp | 1 | 2 | 3 | 3 | 4 |
| cnt | 1 | 1 | 1 | 1 | 2 |
maxLen = 4. Sirf i = 4 pe dp[i] == 4. Toh total = cnt[4] = 2 ✅
Case B wala moment
i=4, j=3 pe: dp[3] = 3 (chain [1,3,4]) aur dp[4] already 4 tha (chain [1,3,5,7] se). 1 + 3 == 4 → Case B → cnt[4] += cnt[3] → 1 + 1 = 2.
Do chains: [1,3,5,7] aur [1,3,4,7] ✅
Doosra example — sab barabar
nums = [2,2,2,2,2]: koi nums[j] < nums[i] nahi, toh sab dp[i] = 1, cnt[i] = 1. maxLen = 1, aur paanchon indices pe dp[i] == 1 → total = 5 ✅
Yahi wo case hai jahan final scan zaroori hai — ek index dekh ke 1 aa jaata.
🎉 Phase 6 complete
| # | Sawaal | Pre-step | Condition | Extra |
|---|---|---|---|---|
| 41 | LIS | — | arr[j] < arr[i] | — |
| 42 | Print LIS | — | same | hash[] + backtrack |
| 43 | LIS O(n log n) | — | binary search — DP nahi | |
| 44 | Divisible Subset | sort | arr[i] % arr[j] == 0 | hash[] |
| 45 | String Chain | sort by length | isPredecessor() | — |
| 46 | Bitonic | — | same, dono disha | dp1 + dp2 − 1 |
| 47 | Number of LIS | — | same | cnt[] array |
Saat sawaal, ek loop. Q41 ka 10-line ka code likh lo. Baaki sab me ya toh condition badalti hai (Q44, Q45), ya ek extra array add hota hai (Q42, Q46, Q47).
Khud try karo
- Case A me
cnt[i] = cnt[j]ki jagahcnt[i] += cnt[j]kar do — kaunsa input galat aayega?[1,3,5,4,7]pe test karo. - Final scan hata ke sirf
cnt[lastMaxIndex]return karo —[2,2,2,2,2]pe 1 aayega (galat). - Ye O(n log n) me bhi hota hai (segment tree se). Bahut advanced hai — par soch ke dekho ki count ko binary search ke saath kaise maintain karoge.
Phase 7 · Q48–Q53
MCM / Partition DP
Phase ka sabse tedha hissa — aur sabse zyada "aha" moments wala. State ab ek index nahi, ek interval (i, j) hai. Aur us interval ke andar hum har possible jagah pe todte hain, dono tukdon ko recursively solve karte hain, aur best combination lete hain.
if (i == j) return BASE // ek hi element
best = INIT
for k = i to j-1: // har jagah todo
steps = f(i, k) + f(k+1, j) + COST(i, k, j)
best = min/max(best, steps)
return best
// Tabulation: i ULTA (n-1 → 0), j SEEDHA (i+1 → n-1)
Matrix Chain Multiplication
GFG classic · poore phase ka naam isi pe hai
Problem
Array arr[] me matrices ke dimensions hain. n elements ka matlab n-1 matrices:
Matrix i ka size = arr[i-1] × arr[i]
Matrix multiplication associative hai — bracket kahin bhi laga sakte ho, answer wahi. Par operations ki ginti badal jaati hai. Minimum operations nikalo.
arr = [10, 20, 30, 40, 50] → 4 matrices: A(10×20), B(20×30), C(30×40), D(40×50) → answer 38000
(a × b) aur (b × c) ko multiply karne me a × b × c operations lagte hain, aur result (a × c) hota hai.
Soch — bracket kahan lagayein
Q41 tak har sawaal me hum "index pe kya karna hai" soch rahe the. Yahan sawaal alag hai: "is chain ko kahan todun?"
f(i, j) = "matrix i se matrix j tak multiply karne ka minimum cost"
Chain i..j ko kisi bhi k pe tod sakte ho:
(A_i ... A_k) × (A_k+1 ... A_j)
- Left tukda solve karo →
f(i, k), result sizearr[i-1] × arr[k] - Right tukda solve karo →
f(k+1, j), result sizearr[k] × arr[j] - Ab in dono ko multiply karo → cost
arr[i-1] × arr[k] × arr[j]
f(i, j) = min over k = i to j-1 of
f(i, k) + f(k+1, j) + arr[i-1] * arr[k] * arr[j]
Matrices 1 se n-1 tak number hoti hain, 0 se nahi. Kyunki matrix i ke liye arr[i-1] chahiye — aur arr[-1] hota nahi.
Toh initial call: f(1, n-1), na ki f(0, n-1).
Base case
if (i == j) return 0; // ek hi matrix, multiply karne ko kuch nahi
Code
static int f(int i, int j, int[] arr, int[][] dp) {
if (i == j) return 0;
if (dp[i][j] != -1) return dp[i][j];
int mini = Integer.MAX_VALUE;
for (int k = i; k <= j - 1; k++) {
int steps = arr[i - 1] * arr[k] * arr[j]
+ f(i, k, arr, dp)
+ f(k + 1, j, arr, dp);
mini = Math.min(mini, steps);
}
return dp[i][j] = mini;
}
// driver
int n = arr.length;
int[][] dp = new int[n][n];
for (int[] row : dp) Arrays.fill(row, -1);
System.out.println(f(1, n - 1, arr, dp));
static int matrixMultiplication(int[] arr, int n) {
int[][] dp = new int[n][n];
// base: i == j → 0 (Java default ✅)
for (int i = n - 1; i >= 1; i--) { // ⚠️ i ULTA
for (int j = i + 1; j < n; j++) { // ⚠️ j SEEDHA, i+1 se
int mini = Integer.MAX_VALUE;
for (int k = i; k <= j - 1; k++) {
int steps = arr[i - 1] * arr[k] * arr[j]
+ dp[i][k]
+ dp[k + 1][j];
mini = Math.min(mini, steps);
}
dp[i][j] = mini;
}
}
return dp[1][n - 1];
}
dp[i][j] ko chahiye dp[i][k] (same row, chhota j) aur dp[k+1][j] (bada i wali row).
Toh i ulta chalao (badi rows pehle bharo) aur j seedha (chhote j pehle). Ye do direction har Phase 7 sawaal me same rahengi — ratt lo.
Aur j hamesha i+1 se shuru hota hai, kyunki j < i wala aadha table use hi nahi hota. Sirf upper triangle bharti hai.
Dry Run
arr = [10, 20, 30, 40, 50], n = 5. Matrices: A(10×20), B(20×30), C(30×40), D(40×50)
Length 2 chains
| Cell | Chain | Cost = arr[i−1]×arr[k]×arr[j] | dp |
|---|---|---|---|
| dp[1][2] | A×B | 10×20×30 | 6000 |
| dp[2][3] | B×C | 20×30×40 | 24000 |
| dp[3][4] | C×D | 30×40×50 | 60000 |
Length 3 chains
dp[1][3] (A×B×C), do tareeke:
| k | Bracket | dp[1][k] + dp[k+1][3] + cost | Total |
|---|---|---|---|
| 1 | A × (BC) | 0 + 24000 + 10×20×40 | 32000 ✅ |
| 2 | (AB) × C | 6000 + 0 + 10×30×40 | 18000 ✅ |
dp[1][3] = min(32000, 18000) = 18000
dp[2][4] (B×C×D):
| k | Bracket | Calculation | Total |
|---|---|---|---|
| 2 | B × (CD) | 0 + 60000 + 20×30×50 | 90000 |
| 3 | (BC) × D | 24000 + 0 + 20×40×50 | 64000 ✅ |
dp[2][4] = 64000
Final — dp[1][4] (poori chain)
| k | Bracket | dp[1][k] + dp[k+1][4] + arr[0]×arr[k]×arr[4] | Total |
|---|---|---|---|
| 1 | A × (BCD) | 0 + 64000 + 10×20×50 = 0 + 64000 + 10000 | 74000 |
| 2 | (AB) × (CD) | 6000 + 60000 + 10×30×50 = 6000+60000+15000 | 81000 |
| 3 | (ABC) × D | 18000 + 0 + 10×40×50 = 18000+0+20000 | 38000 ✅ |
Answer = 38000 ✅ — best bracketing: ((A×B)×C)×D
Poori table
| i \ j | 1 | 2 | 3 | 4 |
|---|---|---|---|---|
| 1 | 0 | 6000 | 18000 | 38000 |
| 2 | — | 0 | 24000 | 64000 |
| 3 | — | — | 0 | 60000 |
| 4 | — | — | — | 0 |
Sirf upper triangle bhari — aur diagonal poori zeros. Ye Phase 7 ki har table ki shakl hai.
Complexity
O(n²) states, har state pe O(n) ka k loop → O(n³). Space O(n²).
Space optimization possible nahi — dp[i][j] ko poore table se cells chahiye, sirf ek row se kaam nahi chalta. Ye Phase 7 ki general baat hai.
Khud try karo
f(0, n-1)se call karke dekho —arr[-1]pe crash. Isliye1se shuru hota hai.- Tabulation me
iko seedha chala do — galat answer aayega (cells abhi tak nahi bhari hongi). - Best bracketing print karo:
((AB)C)D. Hint: har cell ke liye bestkyaad rakho, phir recursively print karo.
Minimum Cost to Cut a Stick
LeetCode 1547 (hard) · MCM ka twin
Problem
n length ka stick hai. cuts[] array me positions hain jahan cut karna hai. Har cut ka cost = us waqt ke stick ke tukde ki length.
Cuts kisi bhi order me kar sakte ho. Minimum total cost?
n = 7, cuts = [1, 3, 4, 5] → 16
Soch — do preprocessing steps
1 · Array me 0 aur n daalo, phir sort
Har tukde ki length nikalne ke liye uske dono kinare chahiye. Stick ke aakhri kinare 0 aur n hain — wo cuts me nahi hain, par length calculate karne ke liye zaroori hain.
Aur sort isliye ki cuts left-to-right order me hon — tabhi interval DP ka matlab banta hai.
cuts = [1,3,4,5] → [0, 1, 3, 4, 5, 7] (size m = 6)
2 · Ulta socho — ye MCM jaisa hi hai
f(i, j) = "cuts i se j tak karne ka minimum cost, jab stick ka tukda cuts[i-1] se cuts[j+1] tak hai"
Us tukde me pehla cut kaunsa lagayein? Har k try karo:
cost = cuts[j+1] - cuts[i-1] // abhi ke tukde ki poori length
+ f(i, k-1) // left me bache cuts
+ f(k+1, j) // right me bache cuts
1. Cost k pe depend nahi karta — wo poore interval ki length hai. MCM me cost arr[k] pe depend karti thi.
2. k ka loop i se j tak jaata hai (j-1 nahi), kyunki k khud ek cut hai jo consume ho jaata hai — MCM me k ek split point tha jo dono taraf rehta tha.
Base case
if (i > j) return 0; // koi cut baaki nahi
Code
static int f(int i, int j, int[] cuts, int[][] dp) {
if (i > j) return 0;
if (dp[i][j] != -1) return dp[i][j];
int mini = Integer.MAX_VALUE;
for (int k = i; k <= j; k++) {
int cost = cuts[j + 1] - cuts[i - 1]
+ f(i, k - 1, cuts, dp)
+ f(k + 1, j, cuts, dp);
mini = Math.min(mini, cost);
}
return dp[i][j] = mini;
}
static int minCost(int n, int[] cuts) {
int c = cuts.length;
int[] arr = new int[c + 2];
arr[0] = 0; arr[c + 1] = n;
for (int i = 0; i < c; i++) arr[i + 1] = cuts[i];
Arrays.sort(arr);
int[][] dp = new int[c + 1][c + 1];
for (int[] row : dp) Arrays.fill(row, -1);
return f(1, c, arr, dp);
}
static int minCost(int n, int[] cuts) {
int c = cuts.length;
int[] arr = new int[c + 2];
arr[0] = 0; arr[c + 1] = n;
for (int i = 0; i < c; i++) arr[i + 1] = cuts[i];
Arrays.sort(arr);
int[][] dp = new int[c + 2][c + 2];
for (int i = c; i >= 1; i--) { // i ULTA
for (int j = 1; j <= c; j++) { // j SEEDHA
if (i > j) continue; // base case
int mini = Integer.MAX_VALUE;
for (int k = i; k <= j; k++) {
int cost = arr[j + 1] - arr[i - 1]
+ dp[i][k - 1]
+ dp[k + 1][j];
mini = Math.min(mini, cost);
}
dp[i][j] = mini;
}
}
return dp[1][c];
}
Dry Run
n = 7, cuts = [1, 3, 4, 5] → arr = [0, 1, 3, 4, 5, 7], c = 4
Length 1 intervals (single cut)
| Cell | Cut at | Tukda | cost = arr[j+1] − arr[i−1] |
|---|---|---|---|
| dp[1][1] | 1 | 0 → 3 | 3 − 0 = 3 |
| dp[2][2] | 3 | 1 → 4 | 4 − 1 = 3 |
| dp[3][3] | 4 | 3 → 5 | 5 − 3 = 2 |
| dp[4][4] | 5 | 4 → 7 | 7 − 4 = 3 |
Length 2 intervals
dp[1][2] (cuts 1 aur 3, tukda 0→4, length 4):
| k | 4 + dp[1][k−1] + dp[k+1][2] | Total |
|---|---|---|
| 1 | 4 + dp[1][0]=0 + dp[2][2]=3 | 7 ✅ |
| 2 | 4 + dp[1][1]=3 + dp[3][2]=0 | 7 ✅ |
dp[1][2] = 7
Similarly: dp[2][3] = tukda 1→5 (length 4) + best = 6 · dp[3][4] = tukda 3→7 (length 4) + best = 6
Final — dp[1][4] (tukda 0→7, length 7)
| k | Pehla cut | 7 + dp[1][k−1] + dp[k+1][4] | Total |
|---|---|---|---|
| 1 | at 1 | 7 + 0 + dp[2][4] | 7 + 0 + 9 = 16 ✅ |
| 2 | at 3 | 7 + dp[1][1]=3 + dp[3][4]=6 | 7 + 3 + 6 = 16 ✅ |
| 3 | at 4 | 7 + dp[1][2]=7 + dp[4][4]=3 | 17 |
| 4 | at 5 | 7 + dp[1][3] + 0 | 7 + 10 = 17 |
Answer = 16 ✅
Verify — ek optimal order
| Step | Cut at | Tukda | Cost | Running total |
|---|---|---|---|---|
| 1 | 3 | 0→7 (length 7) | 7 | 7 |
| 2 | 1 | 0→3 (length 3) | 3 | 10 |
| 3 | 4 | 3→7 (length 4) | 4 | 14 |
| 4 | 5 | 4→7 (length 3) | 2 | 16 ✅ |
Dhyaan do: pehle beech me cut karne se aage ke tukde chhote ho gaye. Greedy "sabse chhota tukda pehle" yahan kaam nahi karta — isliye DP chahiye.
Khud try karo
0aurndaalna bhool jao —arr[i-1]pe crash ya galat cost. Test karo.Arrays.sort()hata do —cuts = [5,1,4,3]pe galat answer.- Q48 aur Q49 ka
kloop compare karo:k ≤ j-1vsk ≤ j. Socho ki dono mekka matlab kaise alag hai.
Burst Balloons
LeetCode 312 (hard) · Phase 7 ka sabse khoobsurat sawaal
Problem
nums[] me balloons hain. Balloon i phodne pe nums[i-1] × nums[i] × nums[i+1] coins milte hain. Phodne ke baad wo balloon gayab ho jaata hai aur padosi jud jaate hain.
Array ke bahar imaginary 1 maano. Maximum coins?
nums = [3, 1, 5, 8] → 167
Soch — seedha sochna kyun FAIL hota hai
Instinct: "balloon k ko pehle phodta hoon, phir left aur right ko alag-alag solve karta hoon."
Problem: k phodne ke baad uske padosi jud jaate hain. Ab left aur right ke tukde independent nahi rahe — left ka aakhri balloon right ke pehle balloon ka padosi ban gaya.
Aur DP tabhi kaam karta hai jab subproblems independent hon. Toh ye soch tootti hai. 💀
Sawaal palat do: "balloon k ko SABSE AAKHIR me phodta hoon."
Agar k aakhri hai, toh jab uski baari aayegi tab uske dono taraf sirf interval ke boundaries bachi hongi — nums[i-1] aur nums[j+1]. Ye fixed hain, kyunki wo interval ke bahar hain aur abhi phoote nahi.
Aur ab left tukda (i..k-1) aur right tukda (k+1..j) bilkul independent hain. ✅
f(i, j) = max over k = i to j of
nums[i-1] * nums[k] * nums[j+1] // k aakhri me phoota
+ f(i, k-1) // left, pehle
+ f(k+1, j) // right, pehle
Preprocessing
Array ke dono taraf 1 daal do — taaki nums[i-1] aur nums[j+1] hamesha valid rahen. (Q49 me 0 aur n daale the — wahi idea.)
[3,1,5,8] → [1, 3, 1, 5, 8, 1]
Base case
if (i > j) return 0;
Code
static int f(int i, int j, int[] a, int[][] dp) {
if (i > j) return 0;
if (dp[i][j] != -1) return dp[i][j];
int maxi = Integer.MIN_VALUE;
for (int k = i; k <= j; k++) {
int coins = a[i - 1] * a[k] * a[j + 1] // k AAKHRI me
+ f(i, k - 1, a, dp)
+ f(k + 1, j, a, dp);
maxi = Math.max(maxi, coins);
}
return dp[i][j] = maxi;
}
static int maxCoins(int[] nums) {
int n = nums.length;
int[] a = new int[n + 2];
a[0] = 1; a[n + 1] = 1; // dono taraf 1
for (int i = 0; i < n; i++) a[i + 1] = nums[i];
int[][] dp = new int[n + 2][n + 2];
for (int[] row : dp) Arrays.fill(row, -1);
return f(1, n, a, dp);
}
static int maxCoins(int[] nums) {
int n = nums.length;
int[] a = new int[n + 2];
a[0] = 1; a[n + 1] = 1;
for (int i = 0; i < n; i++) a[i + 1] = nums[i];
int[][] dp = new int[n + 2][n + 2];
for (int i = n; i >= 1; i--) { // i ULTA
for (int j = 1; j <= n; j++) { // j SEEDHA
if (i > j) continue;
int maxi = Integer.MIN_VALUE;
for (int k = i; k <= j; k++) {
int coins = a[i - 1] * a[k] * a[j + 1]
+ dp[i][k - 1]
+ dp[k + 1][j];
maxi = Math.max(maxi, coins);
}
dp[i][j] = maxi;
}
}
return dp[1][n];
}
Dry Run
nums = [3, 1, 5, 8] → a = [1, 3, 1, 5, 8, 1] (indices 0–5), n = 4
Length 1 intervals
| Cell | k | a[i−1] × a[k] × a[j+1] | dp |
|---|---|---|---|
| dp[1][1] | 1 | 1 × 3 × 1 | 3 |
| dp[2][2] | 2 | 3 × 1 × 5 | 15 |
| dp[3][3] | 3 | 1 × 5 × 8 | 40 |
| dp[4][4] | 4 | 5 × 8 × 1 | 40 |
Length 2 intervals
dp[1][2] (balloons 3, 1 — boundaries a[0]=1, a[3]=5):
| k | Aakhri phoota | a[0]×a[k]×a[3] + dp[1][k−1] + dp[k+1][2] | Total |
|---|---|---|---|
| 1 | 3 | 1×3×5 + 0 + dp[2][2]=15 | 15 + 15 = 30 ✅ |
| 2 | 1 | 1×1×5 + dp[1][1]=3 + 0 | 5 + 3 = 8 |
dp[1][2] = 30
Similarly: dp[2][3] = 135, dp[3][4] = 48
Length 3
dp[1][3] = 159 · dp[2][4] = 159
Final — dp[1][4]
| k | Aakhri phoota | a[0]×a[k]×a[5] + dp[1][k−1] + dp[k+1][4] | Total |
|---|---|---|---|
| 1 | 3 | 1×3×1 + 0 + dp[2][4]=159 | 3 + 159 = 162 |
| 2 | 1 | 1×1×1 + dp[1][1]=3 + dp[3][4]=48 | 1 + 51 = 52 |
| 3 | 5 | 1×5×1 + dp[1][2]=30 + dp[4][4]=40 | 5 + 70 = 75 |
| 4 | 8 | 1×8×1 + dp[1][3]=159 + 0 | 8 + 159 = 167 ✅ |
Answer = 167 ✅
Order verify karo
k=4 jeeta — matlab balloon 8 sabse aakhir me phoota. Uske pehle sab kuch dp[1][3] me hua.
| Step | Phoda | Array | Coins | Total |
|---|---|---|---|---|
| 1 | 1 | [3, 1, 5, 8] → [3,5,8] | 3×1×5 = 15 | 15 |
| 2 | 5 | [3, 5, 8] → [3,8] | 3×5×8 = 120 | 135 |
| 3 | 3 | [3, 8] → [8] | 1×3×8 = 24 | 159 |
| 4 | 8 | [8] → [] | 1×8×1 = 8 | 167 ✅ |
Dhyaan do: sabse bada balloon (8) aakhir me phoota — taaki wo doosron ke multiplication me baar-baar kaam aata rahe. Ye greedy se nikalna namumkin tha.
Khud try karo
- "Pehle phodo" wali soch se code likho aur
[3,1,5,8]pe chalao — galat answer aayega. Ye khud dekhna zaroori hai. - Q49 aur Q50 ka code side-by-side rakho. Structure bilkul same hai — sirf cost formula aur
min/maxka farak. nums = [1, 5]→ 10. Haath se verify karo.
Evaluate Boolean Expression to True
GFG / LeetCode variant · interval DP + ek teesra state
Problem
Ek boolean expression string hai jisme T, F, aur operators &, |, ^ hain. Kitne tareeke se brackets laga sakte ho taaki expression True evaluate ho?
exp = "T|T&F^T" → 4
Soch — Phase 7 + ek naya state
MCM ki tarah har jagah tod sakte hain. Par yahan k sirf operators pe ho sakta hai — operands pe nahi.
Expression hamesha operand operator operand operator ... hota hai. Toh even index pe operands (T/F) aur odd index pe operators. Isliye k loop i+1 se shuru hota hai aur 2 ke step me chalta hai.
Naya state: isTrue
Sirf "kitne tareeke True" ginna kaafi nahi. Kyunki & ke liye humein pata hona chahiye ki left kitne tareeke se True hai aur right kitne tareeke se True hai. Aur | ke liye False wale counts bhi chahiye.
Toh state: f(i, j, isTrue) = "exp[i..j] ko isTrue banane ke kitne tareeke"
Har operator ke rules
Maan lo lT, lF = left ke True/False ways, aur rT, rF = right ke.
| Operator | True ways | False ways |
|---|---|---|
& (AND) | lT × rT | lT×rF + lF×rT + lF×rF |
| (OR) | lT×rT + lT×rF + lF×rT | lF × rF |
^ (XOR) | lT×rF + lF×rT | lT×rT + lF×rF |
Multiply kyun? Kyunki left ke har tareeke ko right ke har tareeke ke saath jod sakte ho — combinations. Aur alag-alag k ke results judte hain (+).
Base case
if (i == j) {
if (isTrue == 1) return exp.charAt(i) == 'T' ? 1 : 0;
else return exp.charAt(i) == 'F' ? 1 : 0;
}
Code
static final int MOD = 1000000007;
static long f(int i, int j, int isTrue, String exp, long[][][] dp) {
if (i > j) return 0;
if (i == j) {
if (isTrue == 1) return exp.charAt(i) == 'T' ? 1 : 0;
else return exp.charAt(i) == 'F' ? 1 : 0;
}
if (dp[i][j][isTrue] != -1) return dp[i][j][isTrue];
long ways = 0;
for (int k = i + 1; k <= j - 1; k += 2) { // sirf operators
long lT = f(i, k - 1, 1, exp, dp);
long lF = f(i, k - 1, 0, exp, dp);
long rT = f(k + 1, j, 1, exp, dp);
long rF = f(k + 1, j, 0, exp, dp);
char op = exp.charAt(k);
if (op == '&') {
if (isTrue == 1) ways += lT * rT;
else ways += lT * rF + lF * rT + lF * rF;
}
else if (op == '|') {
if (isTrue == 1) ways += lT * rT + lT * rF + lF * rT;
else ways += lF * rF;
}
else { // '^'
if (isTrue == 1) ways += lT * rF + lF * rT;
else ways += lT * rT + lF * rF;
}
ways %= MOD;
}
return dp[i][j][isTrue] = ways;
}
// driver
int n = exp.length();
long[][][] dp = new long[n][n][2];
for (long[][] a : dp) for (long[] b : a) Arrays.fill(b, -1);
System.out.println(f(0, n - 1, 1, exp, dp));
1. k += 2 — agar k++ likha toh operands pe bhi todne ki koshish karega aur galat/crash.
2. long aur MOD — counts multiply hote hain, toh tezi se overflow. Har iteration me % MOD lagao.
Dry Run
exp = "T|T&F^T", indices: T(0) |(1) T(2) &(3) F(4) ^(5) T(6)
Base cells (single characters)
| Cell | char | [.][.][1] True ways | [.][.][0] False ways |
|---|---|---|---|
| dp[0][0] | T | 1 | 0 |
| dp[2][2] | T | 1 | 0 |
| dp[4][4] | F | 0 | 1 |
| dp[6][6] | T | 1 | 0 |
Length 3 (ek operator)
| Cell | Expression | op | True ways | False ways |
|---|---|---|---|---|
| dp[0][2] | T|T | | | lT×rT + lT×rF + lF×rT = 1+0+0 = 1 | lF×rF = 0 |
| dp[2][4] | T&F | & | lT×rT = 1×0 = 0 | 0+1+0 = 1 |
| dp[4][6] | F^T | ^ | lT×rF + lF×rT = 0+1 = 1 | 0+0 = 0 |
Length 5
dp[0][4] = "T|T&F", do split points:
| k | op | left | right | True ways |
|---|---|---|---|---|
| 1 | | | T (lT=1,lF=0) | T&F (rT=0,rF=1) | 1×0 + 1×1 + 0×0 = 1 |
| 3 | & | T|T (lT=1,lF=0) | F (rT=0,rF=1) | lT×rT = 1×0 = 0 |
dp[0][4][1] = 1 + 0 = 1 · (aur dp[0][4][0] = 1)
Similarly dp[2][6] = "T&F^T" → True ways 2, False ways 0
Final — dp[0][6][1]
| k | op | left (i..k−1) | right (k+1..j) | True ways |
|---|---|---|---|---|
| 1 | | | T: lT=1, lF=0 | T&F^T: rT=2, rF=0 | 1×2 + 1×0 + 0×2 = 2 |
| 3 | & | T|T: lT=1, lF=0 | F^T: rT=1, rF=0 | lT×rT = 1×1 = 1 |
| 5 | ^ | T|T&F: lT=1, lF=1 | T: rT=1, rF=0 | lT×rF + lF×rT = 0 + 1 = 1 |
Answer = 2 + 1 + 1 = 4 ✅
Khud try karo
- Chaaron bracketings likho aur haath se verify karo ki sab True dete hain.
k += 2kok++kar do — kya hota hai?isTrue = 0se call karo — kitne tareeke False dete hain? (Total bracketings − 4.)
Palindrome Partitioning II
LeetCode 132 (hard) · front partition — naya sub-pattern
Problem
String s ko is tarah cut karo ki har tukda palindrome ho. Minimum cuts kitne lagenge?
s = "aab" → 1 ("aa" | "b")
s = "abcde" → 4 (har character alag)
Soch — front partition
Q48–Q51 me hum interval (i, j) ko beech me todte the — 2D state.
Yahan hum sirf aage se todte hain: "index i se shuru karke, pehla tukda kahan tak?" Baaki string apne aap ek subproblem ban jaati hai. Toh state sirf ek index ka hai — 1D DP.
Ye front partition kehlata hai, aur Q53 bhi isi pe hai.
f(i) = "s[i..n-1] ko palindromes me todne ke liye minimum cuts"
f(i) = min over j = i to n-1, where s[i..j] is palindrome, of
1 + f(j + 1)
Answer me −1 kyun
Recursion har tukde ke baad 1 ginta hai — aakhri tukde ke baad bhi. Par string ke end pe cut lagane ki zaroorat nahi hoti. Toh k tukdon ke liye recursion k deta hai, jabki cuts k-1 hote hain. Isliye final answer f(0) - 1.
Base case
if (i == n) return 0; // string khatam
Code
static boolean isPalindrome(int i, int j, String s) {
while (i < j) {
if (s.charAt(i) != s.charAt(j)) return false;
i++; j--;
}
return true;
}
static int f(int i, int n, String s, int[] dp) {
if (i == n) return 0;
if (dp[i] != -1) return dp[i];
int mini = Integer.MAX_VALUE;
for (int j = i; j < n; j++) {
if (isPalindrome(i, j, s)) {
int cost = 1 + f(j + 1, n, s, dp);
mini = Math.min(mini, cost);
}
}
return dp[i] = mini;
}
// driver
int n = s.length();
int[] dp = new int[n];
Arrays.fill(dp, -1);
System.out.println(f(0, n, s, dp) - 1); // ⚠️ minus 1
static int minCut(String s) {
int n = s.length();
int[] dp = new int[n + 1];
dp[n] = 0; // base case
for (int i = n - 1; i >= 0; i--) { // ULTA
int mini = Integer.MAX_VALUE;
for (int j = i; j < n; j++) {
if (isPalindrome(i, j, s)) {
mini = Math.min(mini, 1 + dp[j + 1]);
}
}
dp[i] = mini;
}
return dp[0] - 1;
}
static int minCut(String s) {
int n = s.length();
// pal[i][j] = kya s[i..j] palindrome hai
boolean[][] pal = new boolean[n][n];
for (int i = n - 1; i >= 0; i--) {
for (int j = i; j < n; j++) {
if (s.charAt(i) == s.charAt(j)) {
// length ≤ 2, ya andar wala bhi palindrome
pal[i][j] = (j - i <= 2) || pal[i + 1][j - 1];
}
}
}
int[] dp = new int[n + 1];
for (int i = n - 1; i >= 0; i--) {
int mini = Integer.MAX_VALUE;
for (int j = i; j < n; j++) {
if (pal[i][j]) mini = Math.min(mini, 1 + dp[j + 1]);
}
dp[i] = mini;
}
return dp[0] - 1;
}
Har baar isPalindrome() chalane me O(n) lagta tha. Precompute karke wo O(1) lookup ban gaya. Palindrome table khud interval DP hai: s[i..j] palindrome hai agar s[i]==s[j] aur s[i+1..j-1] palindrome ho. Interview me ye optimization mention karna — badi baat hai.
Dry Run
s = "aab", n = 3
Palindrome table
| i \ j | 0 (a) | 1 (a) | 2 (b) |
|---|---|---|---|
| 0 (a) | T | T "aa" | F "aab" |
| 1 (a) | — | T | F "ab" |
| 2 (b) | — | — | T |
dp array — ulta bharte hain
| i | Kaunse j palindrome | 1 + dp[j+1] | dp[i] |
|---|---|---|---|
| 3 | base | — | 0 |
| 2 | j=2 ("b" ✅) | 1 + dp[3] = 1+0 | 1 |
| 1 | j=1 ("a" ✅) | 1 + dp[2] = 1+1 | 2 |
| 0 | j=0 ("a" ✅) | 1 + dp[1] = 1+2 = 3 | 2 |
| j=1 ("aa" ✅) | 1 + dp[2] = 1+1 = 2 ✅ |
dp[0] = 2 → answer = 2 − 1 = 1 ✅
Verify
dp[0] = 2 ka matlab: 2 tukde lage. Tukde: "aa" aur "b". Toh cuts = 2 - 1 = 1 ✅
j=0 wala raasta ("a" | "a" | "b") 3 tukde deta — 2 cuts. DP ne behtar wala chuna.
Khud try karo
-1hata do —"aab"pe 2 aayega (galat).s = "aaaa"→ 0 (poori string hi palindrome hai).- Actual partitions print karo:
["aa", "b"]. Harike liye bestjyaad rakho. - Palindrome Partitioning I (LeetCode 131) — saare valid partitions list karo. Wo DP nahi, backtracking hai. Dono ka farak samajhna zaroori hai.
Partition Array for Maximum Sum
LeetCode 1043 · front partition, window ke saath
Problem
Array ko subarrays me baanto, har subarray ki length at most k. Har subarray ke saare elements uske maximum se replace ho jaate hain. Final array ka maximum sum?
arr = [1, 15, 7, 9, 2, 5, 10], k = 3 → 84
Partition: [1,15,7] [9] [2,5,10] → [15,15,15, 9, 10,10,10] → sum = 45 + 9 + 30 = 84 ✅
Soch — Q52 ka twin
Wahi front partition: "index i se shuru karke, pehla subarray kitna lamba?" — 1 se k tak.
f(i) = max over len = 1 to k (aur i+len ≤ n) of
len * (max in arr[i..i+len-1]) + f(i + len)
Har len ke liye alag se max nikalna O(k) extra lagata. Par jaise-jaise len badhta hai, window ek element se badhti hai — toh maxi = Math.max(maxi, arr[i + len - 1]) se O(1) me update ho jaata hai. Ye chhota sa trick O(n k²) ko O(n k) bana deta hai.
Base case
if (i == n) return 0;
Yahan -1 wala jhol nahi hai (Q52 ke ulta), kyunki hum cuts nahi sum gin rahe hain.
Code
static int f(int i, int n, int k, int[] arr, int[] dp) {
if (i == n) return 0;
if (dp[i] != -1) return dp[i];
int maxi = Integer.MIN_VALUE;
int best = Integer.MIN_VALUE;
for (int len = 1; len <= k && i + len - 1 < n; len++) {
maxi = Math.max(maxi, arr[i + len - 1]); // window ka max
int sum = len * maxi + f(i + len, n, k, arr, dp);
best = Math.max(best, sum);
}
return dp[i] = best;
}
// driver
int[] dp = new int[n];
Arrays.fill(dp, -1);
System.out.println(f(0, n, k, arr, dp));
static int maxSumAfterPartitioning(int[] arr, int k) {
int n = arr.length;
int[] dp = new int[n + 1];
dp[n] = 0;
for (int i = n - 1; i >= 0; i--) { // ULTA
int maxi = Integer.MIN_VALUE;
int best = Integer.MIN_VALUE;
for (int len = 1; len <= k && i + len - 1 < n; len++) {
maxi = Math.max(maxi, arr[i + len - 1]);
int sum = len * maxi + dp[i + len];
best = Math.max(best, sum);
}
dp[i] = best;
}
return dp[0];
}
Dry Run
arr = [1, 15, 7, 9, 2, 5, 10], k = 3, n = 7. Ulta bharte hain.
| i | len=1 | len=2 | len=3 | dp[i] |
|---|---|---|---|---|
| 7 | base | 0 | ||
| 6 (10) | 1×10 + 0 = 10 | — | — | 10 |
| 5 (5) | 1×5 + 10 = 15 | 2×10 + 0 = 20 | — | 20 |
| 4 (2) | 1×2 + 20 = 22 | 2×5 + 10 = 20 | 3×10 + 0 = 30 | 30 |
| 3 (9) | 1×9 + 30 = 39 | 2×9 + 20 = 38 | 3×9 + 10 = 37 | 39 |
| 2 (7) | 1×7 + 39 = 46 | 2×9 + 30 = 48 | 3×9 + 20 = 47 | 48 |
| 1 (15) | 1×15 + 48 = 63 | 2×15 + 39 = 69 | 3×15 + 30 = 75 | 75 |
| 0 (1) | 1×1 + 75 = 76 | 2×15 + 48 = 78 | 3×15 + 39 = 84 | 84 |
Answer = dp[0] = 84 ✅
Partition trace karo
dp[0] = 84 aaya len=3 se → pehla subarray [1,15,7], max 15, contribution 45. Phir dp[3] = 39.
dp[3] = 39 aaya len=1 se → [9], contribution 9. Phir dp[4] = 30.
dp[4] = 30 aaya len=3 se → [2,5,10], max 10, contribution 30. Phir dp[7] = 0.
Partition: [1,15,7] [9] [2,5,10] → 45 + 9 + 30 = 84 ✅
Interesting observation
i=1 pe len=3 jeeta ([15,7,9], sab 15 ban gaye = 45). Chhoti value 7 ko bade padosi ke saath rakh ke uska faayda uthaya. Ye greedy se nahi milta — greedy har chhote element ko akela chhodne ki koshish karta.
🎉 Phase 7 complete
| # | Sawaal | Type | State | Combine |
|---|---|---|---|---|
| 48 | MCM | interval | i, j | min |
| 49 | Cut a Stick | interval | i, j | min |
| 50 | Burst Balloons | interval | i, j | max |
| 51 | Boolean Evaluation | interval | i, j, isTrue | + (count) |
| 52 | Palindrome Partitioning II | front | i (1D) | min |
| 53 | Partition for Max Sum | front | i (1D) | max |
Phase 7 ke do sub-patterns
| Interval partition (Q48–51) | Front partition (Q52–53) | |
|---|---|---|
| State | (i, j) — 2D | i — 1D |
| Todte kahan | interval ke beech me | hamesha aage se |
| Recursion | f(i,k) + f(k+1,j) | cost + f(j+1) |
| Complexity | O(n³) | O(n × k) |
| Pehchan | "bracket lagao", "order chuno" | "pehla tukda kahan tak" |
Pehchanne ka tareeka: agar tukdon ka combine hone ka order matter karta hai (MCM, balloons) → interval. Agar bas kahan todna hai matter karta hai aur tukde independent hain (palindrome, subarray) → front.
Khud try karo
k = 1daalo → answer = poora array ka sum (49). Har element akela.k = 7(poori length) →7 × 15 =105.- Q52 aur Q53 ka code side-by-side rakho. Structure identical hai —
minvsmax, palindrome-check vs window-max, aur-1vs kuch nahi.
Phase 8 · Q54–Q55
DP on Squares
Chhota phase, do sawaal — par dono interview me aksar aate hain. Idea ek hi hai: har cell ko bottom-right corner maan ke pucho, "yahan khatam hone wala sabse bada square/rectangle kitna bada hai?" Bilkul Phase 6 ka "i pe khatam" wala dhaancha, 2D me.
dp[i][j] = 1 + min( dp[i-1][j], dp[i][j-1], dp[i-1][j-1] )
else
dp[i][j] = 0 // reset — Q27 jaisa
answer = sum / max over all cells
Count Square Submatrices with All Ones
LeetCode 1277 · min of three neighbours
Problem
Binary matrix me kitne square submatrices hain jinme saare 1 hain? (Har size ke — 1×1, 2×2, 3×3...)
matrix = 0 1 1 1
1 1 1 1
0 1 1 1
answer = 15
Soch — do insights
1 · dp[i][j] = "yahan khatam hone wala sabse bada square"
dp[i][j] = "cell (i,j) ko bottom-right corner maan ke, sabse bade all-ones square ki side length"
Agar dp[i][j] = 3 hai, toh us corner pe teen squares khatam hote hain: 1×1, 2×2, aur 3×3.
Matlab dp[i][j] ki value hi us cell pe khatam hone wale squares ki ginti hai!
Toh answer = poori table ka sum. Alag se counting logic likhne ki zaroorat hi nahi. 🎯
2 · Recurrence — teen padosi ka minimum
(i,j) pe k size ka square tabhi ban sakta hai jab uske teeno padosi bhi kam se kam k-1 size ka square rakh sakte hon:
dp[i-1][j]— upardp[i][j-1]— leftdp[i-1][j-1]— diagonal
Minimum lete hain kyunki sabse kamzor kadi hi decide karti hai. Ek bhi chhota hua toh square utna hi bada ban paayega.
if (matrix[i][j] == 1)
dp[i][j] = 1 + min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1]);
else
dp[i][j] = 0;
Base case
Pehli row aur pehla column: wahan square sirf 1×1 hi ho sakta hai, toh dp[i][j] = matrix[i][j] (0 ya 1).
Code
static int countSquares(int[][] matrix) {
int n = matrix.length, m = matrix[0].length;
int[][] dp = new int[n][m];
// base: pehli row
for (int j = 0; j < m; j++) dp[0][j] = matrix[0][j];
// base: pehla column
for (int i = 0; i < n; i++) dp[i][0] = matrix[i][0];
for (int i = 1; i < n; i++) {
for (int j = 1; j < m; j++) {
if (matrix[i][j] == 0) {
dp[i][j] = 0;
} else {
dp[i][j] = 1 + Math.min(dp[i - 1][j],
Math.min(dp[i][j - 1], dp[i - 1][j - 1]));
}
}
}
// answer = poori table ka sum
int sum = 0;
for (int i = 0; i < n; i++)
for (int j = 0; j < m; j++)
sum += dp[i][j];
return sum;
}
matrix me hi likh do (agar modify allowed hai) → O(1) extra space. Interview me poochna ki input modify kar sakte hain ya nahi.
Dry Run
| matrix | 0 | 1 | 2 | 3 |
|---|---|---|---|---|
| 0 | 0 | 1 | 1 | 1 |
| 1 | 1 | 1 | 1 | 1 |
| 2 | 0 | 1 | 1 | 1 |
| dp | 0 | 1 | 2 | 3 |
|---|---|---|---|---|
| 0 | 0 | 1 | 1 | 1 |
| 1 | 1 | 1 | 2 | 2 |
| 2 | 0 | 1 | 2 | 3 |
Cell by cell — important wale
| Cell | matrix | up, left, diag | 1 + min | dp |
|---|---|---|---|---|
| dp[1][1] | 1 | dp[0][1]=1, dp[1][0]=1, dp[0][0]=0 | 1 + 0 | 1 |
| dp[1][2] | 1 | dp[0][2]=1, dp[1][1]=1, dp[0][1]=1 | 1 + 1 | 2 |
| dp[2][2] | 1 | dp[1][2]=2, dp[2][1]=1, dp[1][1]=1 | 1 + 1 | 2 |
| dp[2][3] | 1 | dp[1][3]=2, dp[2][2]=2, dp[1][2]=2 | 1 + 2 | 3 |
dp[1][1] = 1 hi raha kyunki diagonal dp[0][0] = 0 tha — wahan 0 hai matrix me. Ek kamzor corner ne poora 2×2 rok diya.
Sum nikalo
Row 0: 0+1+1+1 = 3
Row 1: 1+1+2+2 = 6
Row 2: 0+1+2+3 = 6
Total = 3 + 6 + 6 = 15 ✅
Verify — ginti check
| Size | Kitne |
|---|---|
| 1×1 | 10 (matrix me 10 ones hain) |
| 2×2 | 4 |
| 3×3 | 1 |
| Total | 15 ✅ |
Khud try karo
- Maximal Square (LeetCode 221) — sabse bade square ka area. Same dp, bas
sumki jagahmax(dp[i][j])lo aur square kar do. Do line ka farak. minki jagahmaxlagao — kya galat aata hai? Kyun?- Sirf do padosi (up, left) lo, diagonal chhod do — kaunsa case tootega? (Diagonal pe hole ho toh galat bada square gina jaayega.)
Maximal Rectangle
LeetCode 85 (hard) · DP nahi — histogram + stack
Problem
Binary matrix me sabse bade rectangle (square nahi) ka area jisme saare 1 hain.
matrix = 1 0 1 0 0
1 0 1 1 1
1 1 1 1 1
1 0 0 1 0
answer = 6
Soch — har row ko histogram maano
Har row ko base maano, aur uske upar column-wise consecutive 1s ki height gino. Ab har row ek histogram ban gaya.
Toh sawaal ban gaya: "n histograms me se har ek me largest rectangle nikalo, sabka max lo".
Height array kaise banega:
if (matrix[i][j] == 1) heights[j] += 1;
else heights[j] = 0; // reset — chain tooti
Ye Q27 (Common Substring) wala hi reset-on-zero pattern hai, columns pe.
Largest Rectangle in Histogram — monotonic stack
Har bar i ke liye do cheezein chahiye: uske left me pehla chhota bar (PSE) aur right me pehla chhota bar (NSE). Toh us bar ki poori width = NSE - PSE - 1, aur area = height[i] × width.
Monotonic increasing stack se ye ek hi pass me O(n) me ho jaata hai.
Phase 8 me hone ke bawajood, Q55 ka core stack hai, DP nahi. Interview me isko "DP problem" kehna galat impression deta hai. Sahi baat: "matrix ko histograms me reduce karke, har histogram pe monotonic stack".
Code
static int largestRectangleArea(int[] heights) {
int m = heights.length;
Stack<Integer> st = new Stack<>();
int maxArea = 0;
for (int i = 0; i <= m; i++) {
int currHeight = (i == m) ? 0 : heights[i]; // sentinel
while (!st.isEmpty() && heights[st.peek()] >= currHeight) {
int height = heights[st.pop()];
// width: stack khaali hai toh poora left available
int width = st.isEmpty() ? i : i - st.peek() - 1;
maxArea = Math.max(maxArea, height * width);
}
st.push(i);
}
return maxArea;
}
static int maximalRectangle(char[][] matrix) {
int n = matrix.length, m = matrix[0].length;
int[] heights = new int[m];
int maxArea = 0;
for (int i = 0; i < n; i++) {
// is row ke liye histogram update karo
for (int j = 0; j < m; j++) {
if (matrix[i][j] == '1') heights[j]++;
else heights[j] = 0; // reset
}
maxArea = Math.max(maxArea, largestRectangleArea(heights));
}
return maxArea;
}
Loop i <= m tak chalta hai aur aakhri iteration me currHeight = 0 maan leta hai. Isse stack me bache hue saare bars apne aap pop ho jaate hain — alag se cleanup loop likhne ki zaroorat nahi.
Dry Run
matrix upar wali. Row-by-row heights:
| Row | matrix row | heights[] | Largest rect in histogram |
|---|---|---|---|
| 0 | 1 0 1 0 0 | [1, 0, 1, 0, 0] | 1 |
| 1 | 1 0 1 1 1 | [2, 0, 2, 1, 1] | 3 |
| 2 | 1 1 1 1 1 | [3, 1, 3, 2, 2] | 6 ✅ |
| 3 | 1 0 0 1 0 | [4, 0, 0, 3, 0] | 4 |
Row 2 ka histogram — kahan se 6 aaya
heights = [3, 1, 3, 2, 2]
| Bar | height | Kitni door tak faila (width) | Area |
|---|---|---|---|
| 0 | 3 | 1 (bar 1 chhota hai) | 3 |
| 1 | 1 | 5 (poora) | 5 |
| 2 | 3 | 1 | 3 |
| 3 | 2 | 3 (bars 2,3,4 ≥ 2) | 6 ✅ |
| 4 | 2 | 3 | 6 |
Answer = 6 ✅
Matrix me wo rectangle kahan hai
Rows 1–2, columns 2–4 → 2 rows × 3 columns = 6:
row 1: . . 1 1 1
row 2: . . 1 1 1
Row 3 pe reset dekho
heights = [4, 0, 0, 3, 0] — columns 1, 2, 4 me 0 aaya toh height reset ho gayi. Yahi heights[j] = 0 wali line kar rahi hai.
🎉 Phase 8 complete
| # | Sawaal | Technique | Time |
|---|---|---|---|
| 54 | Count Square Submatrices | DP: 1 + min(3 padosi) | O(n × m) |
| Maximal Square (LC 221) | same DP, max × max | O(n × m) | |
| 55 | Maximal Rectangle | histogram + stack | O(n × m) |
Khud try karo
- Pehle Largest Rectangle in Histogram (LeetCode 84) akela solve karo. Q55 uske bina samajh nahi aayega.
- Sentinel (
i <= m) hata do aur alag cleanup loop likho — same answer aana chahiye, par code lamba ho jaayega. heights[j] = 0ki jagahheights[j]waisa hi chhod do — kaunsa test case tootega?
Phase 9 · Q56–Q58
Advanced DP
Teen alag-alag techniques jo array/string ke bahar jaati hain: DP tree pe, DP subset pe (bitmask), aur DP digits pe. Ye aksar senior roles aur CP me aati hain. Har ek ka apna dhaancha hai — par soch wahi purani: "state kya hai, aur choices kya hain?"
DP on Trees — House Robber III
LeetCode 337 · Q5 (House Robber) ka tree version
Problem
Binary tree ke har node me paisa hai. Do directly connected nodes (parent-child) ko ek saath loot nahi sakte. Maximum kitna loot sakte ho?
3
/ \
2 3
\ \
3 1
answer = 7 (3 + 3 + 1, root aur dono grandchildren)
Soch — Q5 ka hi dhaancha, tree pe
Q5 (House Robber) me array pe do choices thi: lo aur i-2 pe jao, ya chhodo aur i-1 pe jao.
Tree me wahi hai, bas "aage" ka matlab children hai:
- Node ko loot lo →
node.val +dono children ko chhodna padega (par grandchildren le sakte ho) - Node ko chhod do →
0 +dono children me se jo behtar ho wo lo
Har node se ek do-value ka pair return karo:
res[0]= "is subtree ka max, agar ye node loota"res[1]= "is subtree ka max, agar ye node chhoda"
Isse alag HashMap<TreeNode, Integer> memoization ki zaroorat hi nahi — har node ek hi baar visit hota hai. O(n) time, O(h) stack space.
Recurrence
left = solve(node.left)
right = solve(node.right)
// node loota → children chhodne padenge
rob = node.val + left[1] + right[1]
// node chhoda → children ka best lo
skip = max(left[0], left[1]) + max(right[0], right[1])
return [rob, skip]
Node chhoda hai toh children pe koi pabandi nahi — wo chahe toh loot lein, chahe toh na lein. Isliye dono options ka max. Yahan galti se sirf left[0] (rob) lena bahut common bug hai.
Code
static int[] solve(TreeNode node) {
// [0] = ye node loota, [1] = ye node chhoda
if (node == null) return new int[]{0, 0};
int[] left = solve(node.left);
int[] right = solve(node.right);
// loota → children chhodo
int rob = node.val + left[1] + right[1];
// chhoda → children ka best
int skip = Math.max(left[0], left[1])
+ Math.max(right[0], right[1]);
return new int[]{rob, skip};
}
static int rob(TreeNode root) {
int[] ans = solve(root);
return Math.max(ans[0], ans[1]);
}
Kyunki node ka answer nikalne se pehle dono children ka answer chahiye. Left → Right → Node — wahi post-order traversal. Poori Tree DP isi order me chalti hai.
Dry Run
3 (root)
/ \
2 3
\ \
3 1
| Node | left pair | right pair | rob = val + l[1] + r[1] | skip = max(l) + max(r) |
|---|---|---|---|---|
| 3 (leaf, left ka child) | [0,0] | [0,0] | 3 + 0 + 0 = 3 | 0 + 0 = 0 |
| 1 (leaf, right ka child) | [0,0] | [0,0] | 1 + 0 + 0 = 1 | 0 + 0 = 0 |
| 2 | [0,0] | [3,0] | 2 + 0 + 0 = 2 | 0 + max(3,0) = 3 |
| 3 (right child) | [0,0] | [1,0] | 3 + 0 + 0 = 3 | 0 + max(1,0) = 1 |
| 3 (root) | [2,3] | [3,1] | 3 + 3 + 1 = 7 | max(2,3) + max(3,1) = 3+3 = 6 |
Answer = max(7, 6) = 7 ✅
Verify
rob = 7 ka matlab: root (3) + left ka skip (3) + right ka skip (1).
Left ka skip = 3 aaya uske right child (3) se. Right ka skip = 1 aaya uske right child (1) se.
Toh loota: root(3) + grandchild(3) + grandchild(1) = 7 ✅ — koi bhi do directly connected nahi hain.
Har tree DP problem me yahi teen kadam hain:
- Har node ke liye decide karo ki kya-kya information parent ko chahiye (yahan: rob/skip dono).
- Children ko recursively solve karo (post-order).
- Children ki information se apni banao aur return karo.
Khud try karo
- Diameter of Binary Tree (LC 543) — har node se height return karo aur global max update karo. Same dhaancha.
- Binary Tree Maximum Path Sum (LC 124, hard) — har node se "meri taraf se ek hi branch" wala sum return karo, aur global me "dono branch" wala consider karo.
skipmemaxki jagah sirfleft[0] + right[0]likh do — kaunsa tree galat aayega?
Bitmask DP — Assign Tasks / TSP
CP classic · LeetCode 1349, 847, 943 family
Problem — Assign Tasks
n log aur n tasks hain. cost[i][j] = person i ko task j dene ka kharcha. Har person ko exactly ek task. Minimum total cost?
cost = 9 2 7 8
6 4 3 7
5 8 1 8
7 6 9 4
answer = 13 (P0→T1=2, P1→T0=6, P2→T2=1, P3→T3=4)
Soch — subset ko integer me pack karo
Ek n-bit integer ek subset represent kar sakta hai. Bit j set hai = task j assign ho chuka hai.
mask = 0101 (binary) = tasks 0 aur 2 done, tasks 1 aur 3 baaki.
Toh 2ⁿ possible states — aur n ≤ 20 tak ye manageable hai (2²⁰ ≈ 10⁶).
Ek chhota par bahut zaroori observation
mask me kitne bits set hain, wahi batata hai ki kitne log assign ho chuke hain. Toh agla person kaunsa hai, ye mask se hi nikal aata hai:
int person = Integer.bitCount(mask);
Alag se i state rakhne ki zaroorat hi nahi — ye bitmask DP ka classic optimization hai.
Recurrence
f(mask):
person = bitCount(mask)
if (person == n) return 0 // sab assign ho gaye
mini = INF
for task = 0 to n-1:
if (task abhi free hai) // bit set nahi hai
mini = min(mini, cost[person][task] + f(mask | (1 << task)))
return mini
Bit operations — cheat sheet
| Operation | Code | Matlab |
|---|---|---|
| Bit j check | (mask & (1 << j)) != 0 | task j done hai? |
| Bit j set | mask | (1 << j) | task j ko done mark karo |
| Kitne set | Integer.bitCount(mask) | kitne tasks done |
| Sab done | mask == (1 << n) - 1 | poora bhara hua |
Code
static int f(int mask, int n, int[][] cost, int[] dp) {
int person = Integer.bitCount(mask);
if (person == n) return 0;
if (dp[mask] != -1) return dp[mask];
int mini = Integer.MAX_VALUE;
for (int task = 0; task < n; task++) {
if ((mask & (1 << task)) == 0) { // task free hai
int c = cost[person][task]
+ f(mask | (1 << task), n, cost, dp);
mini = Math.min(mini, c);
}
}
return dp[mask] = mini;
}
// driver
int[] dp = new int[1 << n];
Arrays.fill(dp, -1);
System.out.println(f(0, n, cost, dp));
static int assignTasks(int n, int[][] cost) {
int full = (1 << n) - 1;
int[] dp = new int[1 << n];
Arrays.fill(dp, Integer.MAX_VALUE);
dp[full] = 0; // base: sab done
for (int mask = full - 1; mask >= 0; mask--) {
int person = Integer.bitCount(mask);
if (person >= n) continue;
for (int task = 0; task < n; task++) {
if ((mask & (1 << task)) == 0) {
int next = mask | (1 << task);
if (dp[next] != Integer.MAX_VALUE) {
dp[mask] = Math.min(dp[mask],
cost[person][task] + dp[next]);
}
}
}
}
return dp[0];
}
dp[mask] ko dp[next] chahiye, aur next me zyada bits set hain — matlab next > mask. Toh bade masks pehle bharne padte hain. Isliye full se 0 tak ulta loop.
Dry Run
n = 4, cost matrix upar wali. Kuch important states:
| mask (binary) | Tasks done | person = bitCount | dp[mask] |
|---|---|---|---|
| 1111 | sab | 4 | 0 (base) |
| 0111 | 0,1,2 | 3 | cost[3][3] = 4 |
| 1011 | 0,1,3 | 3 | cost[3][2] = 9 |
| 0011 | 0,1 | 2 | min(cost[2][2]+dp[0111], cost[2][3]+dp[1011]) = min(1+4, 8+9) = 5 |
| 0001 | 0 | 1 | min over free tasks... = 11 |
| 0000 | — | 0 | 13 |
dp[0011] ka detail
mask = 0011 → tasks 0, 1 done → person = 2. Free tasks: 2, 3.
| task | cost[2][task] | + dp[next] | Total |
|---|---|---|---|
| 2 | 1 | dp[0111] = 4 | 5 ✅ |
| 3 | 8 | dp[1011] = 9 | 17 |
Final path
| Person | Task | Cost |
|---|---|---|
| P0 | T1 | 2 |
| P1 | T0 | 6 |
| P2 | T2 | 1 |
| P3 | T3 | 4 |
| Total | — | 13 ✅ |
Complexity aur limits
| n | 2ⁿ | 2ⁿ × n | Chalega? |
|---|---|---|---|
| 10 | 1,024 | ~10⁴ | easily |
| 15 | 32,768 | ~5×10⁵ | haan |
| 20 | 1,048,576 | ~2×10⁷ | borderline |
| 25 | 33 million | ~8×10⁸ | nahi |
Agar constraints me n ≤ 20 ya n ≤ 16 dikhe, toh bitmask DP ka strong hint hai. Normal problems me n itna chhota nahi hota.
Khud try karo
- TSP — state
(mask, lastCity). Yahan extra state chahiye kyunki cost "kahan se aaye" pe depend karta hai.dp[1<<n][n]. - LeetCode 1349 (Maximum Students Taking Exam) — row-by-row bitmask, valid seating masks.
personko alag state banao (dp[mask][person]) — kaam karega par memory waste hoga. Kyun? (Kyunkipersonmaskse derive ho jaata hai — redundant state.)
Digit DP
CP classic · LeetCode 233, 357, 902, 1012 family — poore roadmap ka aakhri sawaal
Problem
1 se N tak kitne numbers hain jinke digits ka sum exactly S hai?
N = 25, S = 7 → 3 (7, 16, 25)
Constraints me N 10¹⁸ tak ho sakta hai — toh loop chalana namumkin hai.
Soch — numbers nahi, digits pe chalo
N tak loop O(N) hai — 10¹⁸ pe marega. Par N me sirf 18 digits hain.
Toh number ko left se right, ek-ek digit banao. Har position pe 0 se 9 tak koi bhi digit daal sakte ho — par ek shart ke saath.
Wo shart: "tight"
Maan lo N = 25. Number banate waqt:
| Pehla digit | Doosra digit kitna daal sakte ho | Kyun |
|---|---|---|
2 (= N ka pehla digit) | sirf 0 se 5 | abhi bhi N ki limit pe chipke hain |
0 ya 1 (< N ka) | 0 se 9, poori azaadi | already N se chhota ho chuka hai |
tight = true → ab tak ke saare digits N ke digits ke barabar the. Toh is position pe upper limit digits[pos] hai.
tight = false → kahin pehle chhota digit daal diya tha. Ab number pakka N se chhota hai, toh upper limit 9.
Ek baar tight false ho gaya toh phir kabhi true nahi hota — one-way door.
State aur recurrence
f(pos, sum, tight):
if (pos == len) return (sum == S) ? 1 : 0;
limit = tight ? digits[pos] : 9;
count = 0
for d = 0 to limit:
newTight = tight && (d == limit)
count += f(pos + 1, sum + d, newTight)
return count
tight = true wale states ka raasta unique hota hai — har pos pe sirf ek hi tight path hota hai (N ka prefix). Unko cache karne ka koi faayda nahi, aur agar galat cache kar diya toh alag tight-prefix ke liye galat value mil jaayegi.
Toh: if (!tight) dp[pos][sum] = count; — aur read bhi tabhi karo jab !tight ho.
Code
static int[] digits;
static int S;
static int[][] dp; // dp[pos][sum], sirf tight == false ke liye
static int f(int pos, int sum, boolean tight) {
if (sum > S) return 0; // pruning
if (pos == digits.length) {
return (sum == S) ? 1 : 0;
}
if (!tight && dp[pos][sum] != -1) return dp[pos][sum];
int limit = tight ? digits[pos] : 9;
int count = 0;
for (int d = 0; d <= limit; d++) {
boolean newTight = tight && (d == limit);
count += f(pos + 1, sum + d, newTight);
}
if (!tight) dp[pos][sum] = count; // ⚠️ sirf yahan
return count;
}
static int countNumbers(int N, int targetSum) {
String s = String.valueOf(N);
digits = new int[s.length()];
for (int i = 0; i < s.length(); i++) {
digits[i] = s.charAt(i) - '0';
}
S = targetSum;
dp = new int[s.length()][S + 1];
for (int[] row : dp) Arrays.fill(row, -1);
int ans = f(0, 0, true);
if (S == 0) ans--; // 0 khud count ho gaya tha, hata do
return ans;
}
// Range [L, R] chahiye toh:
// countNumbers(R, S) - countNumbers(L - 1, S)
Is problem me leading zeros se koi farak nahi padta — 07 ka digit sum 7 hi hai, aur wo 7 hi number hai. ✅
Par kuch problems me (jaise "no repeated digits" ya "count digits") leading zeros matter karte hain — tab ek chautha state started (boolean) add karna padta hai: "kya asli number shuru ho chuka hai?"
Dry Run
N = 25, S = 7. digits = [2, 5], len = 2
f(0, 0, true) — pehla digit
limit = digits[0] = 2, toh d chalega 0, 1, 2:
| d | newTight | Recursive call | Andar kya hua | Return |
|---|---|---|---|---|
| 0 | false (0 ≠ 2) | f(1, 0, false) | limit = 9, d=7 pe sum 7 ✅ | 1 |
| 1 | false (1 ≠ 2) | f(1, 1, false) | limit = 9, d=6 pe sum 7 ✅ | 1 |
| 2 | true (2 == 2) | f(1, 2, true) | limit = 5, d=5 pe sum 7 ✅ | 1 |
Total = 1 + 1 + 1 = 3 ✅
Har raaste ka number
| Path | Digits | Number | Digit sum |
|---|---|---|---|
| d=0, then d=7 | 0, 7 | 7 | 7 ✅ |
| d=1, then d=6 | 1, 6 | 16 | 7 ✅ |
| d=2, then d=5 | 2, 5 | 25 | 7 ✅ |
tight ka asar saaf dekho
d=2 wale raaste me tight zinda raha, toh doosre digit ki limit 5 thi — 9 nahi. Isliye 26, 27, 28, 29 jaise numbers explore hi nahi hue. Sahi bhi hai — wo 25 se bade hain.
d=0 aur d=1 wale raston me tight mar gaya, toh poori 0–9 range khuli. Aur wahi se 7 aur 16 mile.
Complexity
States: len × maxSum × 2. Har state pe O(10) ka loop.
N = 10¹⁸, S ≤ 162 (18 digits × 9) → 18 × 163 × 2 × 10 ≈ 6 × 10⁴ operations. Instant.
Brute force 10¹⁸ hota — ye 10¹⁵ guna tez hai.
Har digit DP problem me yahi chaar cheezein decide karni hoti hain:
- pos — kaunsa digit bhar rahe ho (hamesha)
- tight — N ki limit pe chipke ho ya nahi (hamesha)
- problem-specific state — sum, remainder, last digit, used-digits mask, jo bhi chahiye
- started — sirf tab jab leading zeros matter karte hon
Khud try karo
- LeetCode 233 (Number of Digit One) —
1sentak kitne1aate hain. State:(pos, count, tight). - Count numbers divisible by K —
sumki jagahremaindertrack karo:(rem * 10 + d) % K. if (!tight)wali condition hata ke sab memoize kar do — galat answer aayega. KaunseNpe? Test karo, ye sabse zaroori exercise hai.- Range version likho:
[100, 500]me digit sum 5 wale kitne? (f(500) - f(99))
Saat phases, ek cheatsheet
Interview se 15 minute pehle sirf ye padho
Har phase ka core recurrence
| Phase | Q | State | Core recurrence |
|---|---|---|---|
| 3 · Knapsack | 14–24 | (i, target) | COMBINE( f(i−1, t), f(i/i−1, t−a[i]) ) |
| 4 · Strings | 25–34 | (i, j) | match ? 1+f(i−1,j−1) : COMBINE(f(i−1,j), f(i,j−1)) |
| 5 · Stocks | 35–40 | (i, buy[, cap]) | max( ±price[i] + f(i+1, !buy), f(i+1, buy) ) |
| 6 · LIS | 41–47 | (i) | dp[i] = 1 + max(dp[j]) for j < i where COND |
| 7 · Partition | 48–53 | (i, j) ya (i) | min/max over k of f(i,k) + f(k+1,j) + COST |
| 8 · Squares | 54–55 | (i, j) | 1 + min(up, left, diag), warna 0 |
| 9 · Advanced | 56–58 | node / mask / pos | post-order · bitmask · tight |
Rule 1 — Combine operator sawaal se aata hai
| Sawaal kya poochh raha hai | Operator | Invalid pe return |
|---|---|---|
| kya possible hai? | || | false |
| kitne tareeke? | + | 0 |
| minimum? | min | 1e9 |
| maximum? | max | -1e9 / MIN_VALUE |
Invalid wala column sabse zyada bugs deta hai — aur wo silently galat answer dete hain, crash nahi karte.
Rule 2 — Loop directions
| Situation | Direction | Kahan |
|---|---|---|
| 0/1 knapsack, single array | ulta (t = T → 0) | Q15, Q19, Q32 |
| Unbounded, single array | seedha (t = 0 → T) | Q20, Q22, Q23, Q24 |
Recursion i+1 pe jaati hai | tabulation ulta | Phase 5, Q52, Q53 |
| Interval DP | i ulta, j seedha | Q48–Q51 |
| Bitmask | mask ulta (full → 0) | Q57 |
Rule 3 — Answer kahan milta hai
| Answer | Kab | Sawaal |
|---|---|---|
| aakhri cell | state poore input ko cover karti hai | Q14, Q19, Q25, Q33 |
| poori table ka max/sum | dp[i] = "i pe khatam" | Q27, Q41, Q46, Q54 |
| row scan | saare achievable values chahiye | Q16 |
| answer − 1 | recursion ne ek extra gina | Q52 |
Doosri row sabse zyada bugs deti hai. Jab bhi dp[i] ka matlab "i pe khatam" ho, poori table scan karni padegi.
Rule 4 — Zyadatar sawaal disguise me hote hain
| Dikhta hai | Asal me hai |
|---|---|
| Equal Partition (Q15) | Subset Sum, target = S/2 |
| Target Sum (Q21) | Count Partitions, D = target |
| Rod Cutting (Q24) | Unbounded Knapsack, wt = i+1 |
| LPS (Q28) | LCS(s, reverse(s)) |
| Min Insert Palindrome (Q29) | n − LPS |
| Insert/Delete A→B (Q30) | n + m − 2·LCS |
| Divisible Subset (Q44) | sort + LIS, condition badli |
| String Chain (Q45) | sort by length + LIS |
| Bitonic (Q46) | LIS + LIS ulta, −1 |
| Maximal Rectangle (Q55) | n × histogram + stack |
| House Robber III (Q56) | House Robber, post-order pe |
Paintalees me se pandrah se zyada sawaalon me naya DP likhna hi nahi hai — sirf pehchanna hai ki kaunsa purana sawaal hai. Interview me asli skill yahi test hoti hai.
Debug checklist
Answer galat aa raha hai? Ye chhe cheezein order me check karo:
- Invalid pe kya return kar rahe ho?
minme0,maxme bada number — sabse common. - Answer aakhri cell me hai ya poori table me? Phase 6 aur Q27, Q54 me scan chahiye.
- Take me
ihai yai-1? 0/1 aur unbounded mix ho gaye? - Single array me loop direction sahi hai? Ulta vs seedha.
- Base case explicit set kiya? Q33 (Edit Distance) ki pehli row/column zeros nahi hoti.
- Overflow? Counting problems me
long/doublelagao (Q32, Q22, Q51).
Ab bhi na mile: memoization version chalao, poori dp table print karwao, aur haath se banayi table se compare karo. Jis pehle cell pe farak dikhe, bug wahi hai.
Complexity summary
| Phase | Time | Space (optimized) | Space opt possible? |
|---|---|---|---|
| 3 · Knapsack | O(n × target) | O(target) | haan |
| 4 · Strings | O(n × m) | O(min(n,m)) | haan (print chhod ke) |
| 5 · Stocks | O(n × 2 × k) | O(k) | haan |
| 6 · LIS | O(n²) → O(n log n) | O(n) | already O(n) |
| 7 · Partition | O(n³) / O(n×k) | O(n²) / O(n) | interval me nahi |
| 8 · Squares | O(n × m) | O(m) | haan |
| 9 · Advanced | O(n) / O(2ⁿ×n) / O(len×S×10) | O(h) / O(2ⁿ) / O(len×S) | — |
Phases 3–9 · Q14–Q58 · Java · recursion → memoization → tabulation → space optimized