Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 100 additions & 0 deletions src/CoinChangeII.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import java.util.Arrays;

/*
COIN CHANGE II (LeetCode 518)

We solve this problem using multiple approaches:

1) Pure Recursion (Exponential)
2) Top-Down DP (Recursion + Memoization)
3) Bottom-Up DP (2D Matrix)
4) Bottom-Up DP (1D Optimized)

Key Difference from Coin Change I:
- Coin Change I → MIN coins
- Coin Change II → COUNT number of ways

State idea:
dp[i][j] = number of ways to make amount j using first i coins
*/

public class CoinChangeII {
/*1) PURE RECURSION */
public int changeRecursive(int amount, int[] coins) {
return helperRecursive(amount, coins, 0);
}

private int helperRecursive(int amount, int[] coins, int index) {
if (amount == 0) return 1; // found one valid way
if (amount < 0 || index == coins.length) return 0;

// choice: skip coin OR take coin
int skip = helperRecursive(amount, coins, index + 1);
int take = helperRecursive(amount - coins[index], coins, index);

return skip + take;
}

/* 2) TOP-DOWN DP (Memoization) */
private int[][] memo;

public int changeTopDown(int amount, int[] coins) {
memo = new int[coins.length][amount + 1];
for (int i = 0; i < coins.length; i++) {
Arrays.fill(memo[i], -1);
}
return helperTopDown(amount, coins, 0);
}

private int helperTopDown(int amount, int[] coins, int index) {
if (amount == 0) return 1;
if (amount < 0 || index == coins.length) return 0;

if (memo[index][amount] != -1) {
return memo[index][amount];
}

int skip = helperTopDown(amount, coins, index + 1);
int take = helperTopDown(amount - coins[index], coins, index);

memo[index][amount] = skip + take;
return memo[index][amount];
}

/*3) BOTTOM-UP DP (2D Matrix)*/
public int change2D(int amount, int[] coins) {
int n = coins.length;
int[][] dp = new int[n + 1][amount + 1];

// Base case: 1 way to make amount 0 (choose nothing)
for (int i = 0; i <= n; i++) {
dp[i][0] = 1;
}

for (int i = 1; i <= n; i++) {
for (int j = 1; j <= amount; j++) {
if (j < coins[i - 1]) {
dp[i][j] = dp[i - 1][j]; // can't take coin
} else {
dp[i][j] = dp[i - 1][j] + dp[i][j - coins[i - 1]];
}
}
}

return dp[n][amount];
}

/* 4) BOTTOM-UP DP (1D Optimized) */
public int change1D(int amount, int[] coins) {
int[] dp = new int[amount + 1];
dp[0] = 1; // one way to make amount 0

for (int coin : coins) {
for (int j = coin; j <= amount; j++) {
dp[j] += dp[j - coin];
}
}

return dp[amount];
}
}
124 changes: 124 additions & 0 deletions src/PaintHouse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import java.util.Arrays;

/*
PAINT HOUSE (LeetCode 256)

Goal: Min cost to paint all houses such that no two adjacent houses have the same color.
costs[i][c] = cost to paint house i with color c (c in {0,1,2}).

Approaches:
1) Pure Recursion (exponential)
2) Top-Down DP (Memoization)
3) Bottom-Up DP (2D)
4) Bottom-Up DP (O(1) space)

Time:
- Recursion: exponential
- DP: O(n)
Space:
- Memo/2D: O(n)
- O(1) space: constant
*/

public class PaintHouse {

/* 1) PURE RECURSION*/
public int minCostRecursive(int[][] costs) {
if (costs == null || costs.length == 0) return 0;
return Math.min(
helperRec(costs, 0, 0),
Math.min(helperRec(costs, 0, 1), helperRec(costs, 0, 2))
);
}

private int helperRec(int[][] costs, int i, int color) {
int n = costs.length;
if (i == n) return 0;

int costHere = costs[i][color];
if (i == n - 1) return costHere;

int nextMin;
if (color == 0) nextMin = Math.min(helperRec(costs, i + 1, 1), helperRec(costs, i + 1, 2));
else if (color == 1) nextMin = Math.min(helperRec(costs, i + 1, 0), helperRec(costs, i + 1, 2));
else nextMin = Math.min(helperRec(costs, i + 1, 0), helperRec(costs, i + 1, 1));

return costHere + nextMin;
}

/* 2) TOP-DOWN DP (MEMO) */
private int[][] memo;

public int minCostTopDown(int[][] costs) {
if (costs == null || costs.length == 0) return 0;
int n = costs.length;
memo = new int[n][3];
for (int i = 0; i < n; i++) Arrays.fill(memo[i], -1);

return Math.min(
helperMemo(costs, 0, 0),
Math.min(helperMemo(costs, 0, 1), helperMemo(costs, 0, 2))
);
}

private int helperMemo(int[][] costs, int i, int color) {
int n = costs.length;
if (i == n) return 0;
if (memo[i][color] != -1) return memo[i][color];

int costHere = costs[i][color];
int bestNext;

if (i == n - 1) {
memo[i][color] = costHere;
return memo[i][color];
}

if (color == 0) bestNext = Math.min(helperMemo(costs, i + 1, 1), helperMemo(costs, i + 1, 2));
else if (color == 1) bestNext = Math.min(helperMemo(costs, i + 1, 0), helperMemo(costs, i + 1, 2));
else bestNext = Math.min(helperMemo(costs, i + 1, 0), helperMemo(costs, i + 1, 1));

memo[i][color] = costHere + bestNext;
return memo[i][color];
}

/* 3) BOTTOM-UP DP (2D) */
public int minCost2D(int[][] costs) {
if (costs == null || costs.length == 0) return 0;
int n = costs.length;

int[][] dp = new int[n][3];
dp[0][0] = costs[0][0];
dp[0][1] = costs[0][1];
dp[0][2] = costs[0][2];

for (int i = 1; i < n; i++) {
dp[i][0] = costs[i][0] + Math.min(dp[i - 1][1], dp[i - 1][2]);
dp[i][1] = costs[i][1] + Math.min(dp[i - 1][0], dp[i - 1][2]);
dp[i][2] = costs[i][2] + Math.min(dp[i - 1][0], dp[i - 1][1]);
}

return Math.min(dp[n - 1][0], Math.min(dp[n - 1][1], dp[n - 1][2]));
}

/* 4) BOTTOM-UP DP (O(1)) */
public int minCostOptimized(int[][] costs) {
if (costs == null || costs.length == 0) return 0;
int n = costs.length;

int r = costs[0][0];
int g = costs[0][1];
int b = costs[0][2];

for (int i = 1; i < n; i++) {
int newR = costs[i][0] + Math.min(g, b);
int newG = costs[i][1] + Math.min(r, b);
int newB = costs[i][2] + Math.min(r, g);
r = newR;
g = newG;
b = newB;
}

return Math.min(r, Math.min(g, b));
}
}
52 changes: 52 additions & 0 deletions src/TestDP.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
public class TestDP {
public static void main(String[] args) {
// Coin Change II tests
CoinChangeII cc2 = new CoinChangeII();

int[] coins1 = {1, 2, 5};
int amount1 = 5; // ways = 4
int exp1 = 4;

int[] coins2 = {2};
int amount2 = 3; // ways = 0
int exp2 = 0;

int[] coins3 = {10};
int amount3 = 0; // ways = 1 (choose nothing)
int exp3 = 1;

System.out.println(cc2.changeRecursive(amount1, coins1) == exp1);
System.out.println(cc2.changeTopDown(amount1, coins1) == exp1);
System.out.println(cc2.change2D(amount1, coins1) == exp1);
System.out.println(cc2.change1D(amount1, coins1) == exp1);

System.out.println(cc2.change1D(amount2, coins2) == exp2);
System.out.println(cc2.change1D(amount3, coins3) == exp3);

//Paint House tests
PaintHouse ph = new PaintHouse();

int[][] costs1 = {
{17, 2, 17},
{16, 16, 5},
{14, 3, 19}
}; // expected = 10
int expPH1 = 10;

int[][] costs2 = {
{7, 6, 2}
}; // expected = 2
int expPH2 = 2;

int[][] costs3 = {}; // expected = 0
int expPH3 = 0;

System.out.println(ph.minCostRecursive(costs1) == expPH1);
System.out.println(ph.minCostTopDown(costs1) == expPH1);
System.out.println(ph.minCost2D(costs1) == expPH1);
System.out.println(ph.minCostOptimized(costs1) == expPH1);

System.out.println(ph.minCostOptimized(costs2) == expPH2);
System.out.println(ph.minCostOptimized(costs3) == expPH3);
}
}