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
133 changes: 133 additions & 0 deletions src/CoinChange.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
/*
COIN CHANGE I (LeetCode 322)

Problem:
Given coin denominations and a target amount, return the minimum number of coins
required to make that amount. If it is not possible, return -1.

This class demonstrates FOUR different approaches to solve the problem,
starting from brute force recursion and gradually optimizing to Dynamic Programming.

Approaches implemented:
1. Pure Recursive (Brute Force) -> Exponential Time
2. Top-Down DP (Recursion + Memoization) -> O(coins * amount)
3. Bottom-Up DP (2D Table) -> O(coins * amount)
4. Bottom-Up DP (1D Optimized) -> O(coins * amount), Space O(amount)
*/

public class CoinChange {

// Used for memoization in Top-Down DP
int[][] memo;

/*1. PURE RECURSIVE SOLUTION (BRUTE FORCE)
Try all possibilities:
- Either skip the current coin
- Or take the current coin and reduce the amount
Time Complexity: Exponential (very slow)
Space Complexity: Recursion stack
*/
public int recursiveCoinChange(int[] coins, int amount) {
int result = recursiveHelper(coins, amount, 0);
return (result >= Integer.MAX_VALUE - 10) ? -1 : result;
}

private int recursiveHelper(int[] coins, int amount, int idx) {
// Base cases
if (amount == 0) return 0;
if (idx == coins.length || amount < 0) return Integer.MAX_VALUE - 10;

// Choice 1: Do not take current coin
int notChoose = recursiveHelper(coins, amount, idx + 1);

// Choice 2: Take current coin (unbounded)
int choose = recursiveHelper(coins, amount - coins[idx], idx);

return Math.min(notChoose, choose);
}

/* 2. TOP-DOWN DP (MEMOIZATION)
Same recursive logic, but we store results for each state
(idx, amount) to avoid recomputation.
Time Complexity: O(coins * amount)
Space Complexity: O(coins * amount)
*/
public int coinChangeTopDown(int[] coins, int amount) {
memo = new int[coins.length][amount + 1];
int result = topDownHelper(coins, amount, 0);
return (result >= Integer.MAX_VALUE - 10) ? -1 : result;
}

private int topDownHelper(int[] coins, int amount, int idx) {
if (amount == 0) return 0;
if (idx == coins.length || amount < 0) return Integer.MAX_VALUE - 10;

if (memo[idx][amount] != 0) {
return memo[idx][amount];
}

int notChoose = topDownHelper(coins, amount, idx + 1);
int choose = 1 + topDownHelper(coins, amount - coins[idx], idx);

memo[idx][amount] = Math.min(notChoose, choose);
return memo[idx][amount];
}

/* 3. BOTTOM-UP DP (2D TABLE)
dp[i][j] = minimum coins needed to make amount j
using first i coin types
Time Complexity: O(coins * amount)
Space Complexity: O(coins * amount)
*/
public int coinChange2D(int[] coins, int amount) {
int m = coins.length;
int INF = 99999;

int[][] dp = new int[m + 1][amount + 1];

// Base case: no coins, positive amount impossible
for (int j = 1; j <= amount; j++) {
dp[0][j] = INF;
}

// Fill DP table
for (int i = 1; i <= m; i++) {
for (int j = 0; j <= amount; j++) {
if (j < coins[i - 1]) {
dp[i][j] = dp[i - 1][j]; // cannot take coin
} else {
dp[i][j] = Math.min(
dp[i - 1][j], // skip coin
1 + dp[i][j - coins[i - 1]] // take coin
);
}
}
}

return dp[m][amount] == INF ? -1 : dp[m][amount];
}

/* 4. BOTTOM-UP DP (1D OPTIMIZED)
dp[j] = minimum coins needed to make amount j
Optimizes space by reusing previous results.
Time Complexity: O(coins * amount)
Space Complexity: O(amount)
*/
public int coinChange1D(int[] coins, int amount) {
int INF = 99999;
int[] dp = new int[amount + 1];

dp[0] = 0;
for (int j = 1; j <= amount; j++) {
dp[j] = INF;
}

for (int coin : coins) {
for (int j = coin; j <= amount; j++) {
dp[j] = Math.min(dp[j], 1 + dp[j - coin]);
}
}

return dp[amount] == INF ? -1 : dp[amount];
}
}
112 changes: 112 additions & 0 deletions src/HouseRobber.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/*
HOUSE ROBBER (LeetCode 198)

Problem:
Given an array where each element represents money in a house,
find the maximum amount you can rob without robbing two adjacent houses.

This class demonstrates MULTIPLE approaches, starting from brute force
and gradually optimizing to Dynamic Programming.

Approaches implemented:
1. Pure Recursive (Exhaustive Decision Tree)
2. Top-Down DP (Recursion + Memoization)
3. Bottom-Up DP (1D Array)
4. Bottom-Up DP (O(1) Space Optimized)
*/

public class HouseRobber {

// Used for memoization in Top-Down DP
private int[] memo;

/* 1. PURE RECURSIVE SOLUTION (BRUTE FORCE)
At each house:
- Either rob it and skip next
- Or skip it and move to next

Time Complexity: O(2^n)
Space Complexity: O(n) recursion stack
*/
public int robRecursive(int[] nums) {
return robHelper(nums, 0);
}

private int robHelper(int[] nums, int index) {
// Base case: out of bounds
if (index >= nums.length) return 0;

// Choice 1: Do not rob current house
int skip = robHelper(nums, index + 1);

// Choice 2: Rob current house
int rob = nums[index] + robHelper(nums, index + 2);

return Math.max(skip, rob);
}

/*2. TOP-DOWN DP (MEMOIZATION)
Same recursion, but store results to avoid recomputation.

Time Complexity: O(n)
Space Complexity: O(n)
*/
public int robTopDown(int[] nums) {
memo = new int[nums.length];
return robHelperMemo(nums, 0);
}

private int robHelperMemo(int[] nums, int index) {
if (index >= nums.length) return 0;

if (memo[index] != 0) return memo[index];

int skip = robHelperMemo(nums, index + 1);
int rob = nums[index] + robHelperMemo(nums, index + 2);

memo[index] = Math.max(skip, rob);
return memo[index];
}

/* 3. BOTTOM-UP DP (1D ARRAY)
dp[i] = maximum money that can be robbed up to house i
Transition:
dp[i] = max(dp[i-1], nums[i] + dp[i-2])

Time Complexity: O(n)
Space Complexity: O(n)
*/
public int robBottomUp(int[] nums) {
int n = nums.length;
if (n == 0) return 0;
if (n == 1) return nums[0];

int[] dp = new int[n];
dp[0] = nums[0];
dp[1] = Math.max(nums[0], nums[1]);

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

return dp[n - 1];
}

/* 4. BOTTOM-UP DP (SPACE OPTIMIZED)
Uses only two variables instead of dp array.
Time Complexity: O(n)
Space Complexity: O(1)
*/
public int robOptimized(int[] nums) {
int prev2 = 0; // dp[i-2]
int prev1 = 0; // dp[i-1]

for (int money : nums) {
int curr = Math.max(prev1, prev2 + money);
prev2 = prev1;
prev1 = curr;
}

return prev1;
}
}
58 changes: 58 additions & 0 deletions src/TestDP1.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import java.util.Arrays;

public class TestDP1 {
public static void main(String[] args) {

CoinChange cc = new CoinChange();

int[][] coinsList = {
{1, 2, 5},
{2},
{1},
{1, 3, 4}
};
int[] amounts = {11, 3, 0, 6};
int[] expectedCC = {3, -1, 0, 2};

for (int i = 0; i < coinsList.length; i++) {
int r1 = cc.recursiveCoinChange(coinsList[i], amounts[i]);
int r2 = cc.coinChangeTopDown(coinsList[i], amounts[i]);
int r3 = cc.coinChange2D(coinsList[i], amounts[i]);
int r4 = cc.coinChange1D(coinsList[i], amounts[i]);

System.out.println(
r1 == expectedCC[i] &&
r2 == expectedCC[i] &&
r3 == expectedCC[i] &&
r4 == expectedCC[i]
);
}
System.out.println("Testing House Robber");

HouseRobber hr = new HouseRobber();

int[][] robberTests = {
{2, 7, 9, 3, 1},
{1, 2, 3, 1},
{2, 1, 1, 2},
{1},
{}
};

int[] expectedHR = {12, 4, 4, 1, 0};

for (int i = 0; i < robberTests.length; i++) {
int a = hr.robRecursive(robberTests[i]);
int b = hr.robTopDown(robberTests[i]);
int c = hr.robBottomUp(robberTests[i]);
int d = hr.robOptimized(robberTests[i]);

System.out.println(
a == expectedHR[i] &&
b == expectedHR[i] &&
c == expectedHR[i] &&
d == expectedHR[i]
);
}
}
}