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
137 changes: 137 additions & 0 deletions CoinChange.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import java.util.HashMap;
import java.util.Map;

// Time Complexity : Mentioned in each method
// Space Complexity : Mentioned in each method
// Did this code successfully run on Leetcode : Yes
// Any problem you faced while coding this : In creating the recursion tree and identifying recursion method calls

// Approach :
// Since we have to choose out of N coins to make an amount. There can be many combinations the coins can be selected.
// We have to choose the combination which gives the minimum amount of coins. Thus, I have used DP where the basic idea
// is to decide whether to choose coin[i] or choose coin [i-1] and find out whether there sum is equal to the amount.
public class CoinChange {
public int coinChange(int[] coins, int amount) {
if (amount == 0) return 0;
int min = findMinCoinsRecursive(coins, amount);
int minMemo = findMinCoinsMemo(coins, amount, new HashMap<>());
int minIterative = findMinCoinsIterative(coins, amount);
int minIterativeSpace = findMinCoinsIterativeSpace(coins, amount);
return min;
}

public int findMinCoinsRecursive(int[] coins, int amount) {
// N : number of coins, A: given amount
// Time Complexity : O(N^A), if we create recursion tree, at every level, I will need to make choices out n coins,
// so each level will have N^1, N^2, N^3.... N^A recursion calls, going till the amount in the worst case
// Space Complexity : O(A), since the recursion tree will have A levels(also the height of the tree), in worst,
// we will have A stack frames in the recursion stack.

// Base cases, if amount goes below 0, no coin is able to make the amount
if (amount < 0) return -1;
// If amount is 0, the target amount is reached and we should not proceed.
if (amount == 0) return 0;

// Since we need to find min count, initialize with max possible value, +infinity or amount+1
int minCoins = amount + 1;
// Go through all the coins(size A) in the input
for (int coin : coins) {
// calculate the subproblem by selecting this coin, thus removing it from amount
int minCoinsForSubProblem = findMinCoinsRecursive(coins, amount - coin);
if (minCoinsForSubProblem < 0) continue;
// if there is any lesser number of coins from sub problems, override the min count.
if (minCoinsForSubProblem < minCoins) {
minCoins = minCoinsForSubProblem + 1;
}
}

// if minCoins is still max, no subproblem is able to reach target amount, return -1.
return minCoins == amount + 1 ? -1 : minCoins;
}

public int findMinCoinsMemo(int[] coins, int amount, Map<Integer, Integer> memo) {
// N : number of coins, A: given amount
// Time Complexity : O(N*A), in the recursion tree, since we will solve the subproblems and store it in hashmap,
// some of the subtrees can be avoided such that at every level there are N sub problems. So if there are A levels,
// A* N gives us the max number of recursion calls to be made.
// Space Complexity : O(A), since the recursion tree will have A levels(also the height of the tree), in worst,
// we will have A stack frames in the recursion stack.
if (amount < 0) return -1;
if (amount == 0) return 0;

// If the subproblem is already solved, return from memo
if (memo.containsKey(amount)) return memo.get(amount);

int minCoins = amount + 1;
for (int coin : coins) {
int minCoinsForSubProblem = findMinCoinsMemo(coins, amount - coin, memo);
if (minCoinsForSubProblem < 0) continue;
if (minCoinsForSubProblem < minCoins) {
minCoins = minCoinsForSubProblem + 1;
}
}

// save the new computed value in the memo
memo.put(amount, minCoins);
return minCoins == amount+1 ? -1: minCoins;
}

private int findMinCoinsIterative(int[] coins, int amount) {
// N : number of coins, A: given amount
// Time Complexity : O((N+1)*(A+1)) = ~ O(NxA)
// Space Complexity : O(NxA)

int row = coins.length + 1;
int col = amount + 1;
int[][] table = new int[row][col];

// If we need to make 0 amount, we cant use any coins, so initialize with 0
for (int i = 0; i < row; i++) {
table[i][0] = 0;
}

//If we have 0 coins, we need "Infinity" coins to make any amount > 0, I am taking amount +1 as infinity
for (int j = 1; j < col; j++) {
table[0][j] = amount + 1;
}

for (int i = 1; i < row; i++) {
int currentCoin = coins[i - 1];
for (int j = 1; j < col; j++) {
// Take the number of coins excluding the current coin required to make j amount
int excluding = table[i - 1][j];

// If the selected coin value is less than the current amount, include the current coin
if ( currentCoin <= j) {
int including = 1 + table[i][j - currentCoin];
table[i][j] = Math.min(excluding, including);
} else {
table[i][j] = excluding;
}
}
}
// If the last value still has infinity in it, return -1, total amount is not possible;
return table[row - 1][col - 1] == (amount+1) ? -1 : table[row - 1][col - 1];
}

private int findMinCoinsIterativeSpace(int[] coins, int amount) {
// N : number of coins, A: given amount
// Time Complexity : O(A*N)
// Space Complexity : O(A)
int size = amount+1;
int[] table = new int[size];

for(int i = 1; i <= amount; i++){
// Initialize with infinity, in this case we are using a value that is not possible.
table[i] = amount + 1;
for (int currentCoin: coins) {
// if the current coin value is less than the i sum.
if (currentCoin <= i) {
// find min of (value if we include the current coin, the count calculated including the previous coin/excluding the current coin)
table[i] = Math.min(table[i - currentCoin] + 1, table[i]);
}
}
}
return table[amount] == (amount+1) ? -1 : table[amount];
}
}
77 changes: 77 additions & 0 deletions HouseRobber.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import java.util.HashMap;
import java.util.Map;

// Did this code successfully run on Leetcode : Yes
// Any problem you faced while coding this : No
// Approach :
// We need to sum the money based on maximum of the values present at the two neighbors.
// The robber should take the max of the adjacent neighbor or the neighbor's neighbor.
public class HouseRobber {

public int rob(int[] nums) {
// int maxProfit = robMemo(nums, 0, 0, new HashMap<>());
// int maxProfit = robIterative(nums);
// int maxProfit = robIterativeSpace(nums);
return robRecursive(nums, 0, 0);
}
public int robRecursive(int[] nums, int index, int robbedMoney) {
// N: number of elements in the nums
// Time Complexity : O(2^N) because at every method call, we are calling two sub problems and it will for N levels,
// at every level, the recursion calls will be 2^1, 2^2.... 2^N.
// Space Complexity : O(N), the recursion will go N levels deep before hitting the base case,
// so in the call stack we will have at max N frames,

// if we are robbing a house beyond the given array, we are out of bounds and we should take the money and run
if(index >= nums.length) return robbedMoney;

// we should make a decision whether to rob neighbor or neighbor's neighbor, whatever makes you wealthy
return Math.max(robRecursive(nums, index+1, robbedMoney),
robRecursive(nums, index+2, robbedMoney+nums[index]));
}

public int robMemo(int[] nums, int index, int robbedMoney, Map<Integer, Integer> memo) {
// Time Complexity : O(2N) = ~O(N)
// We reduced calls by storing house number and robbed money in the map
// Space Complexity : O(N), the recursion will go N levels deep before hitting the base case

if(index >= nums.length) return robbedMoney;

if(memo.containsKey(index)) return memo.get(index);

int maxWealth = Math.max(robMemo(nums, index+1, robbedMoney, memo),
robMemo(nums, index+2, robbedMoney+nums[index], memo));

memo.put(index, maxWealth);
return maxWealth;
}

// We can iterate over the nums array and calculate value based on previous two neighbors
public int robIterative(int[] nums) {
// Time Complexity : O(N), need to go through all the houses to find max wealth
// Space Complexity : O(N), overrides the current data
int len = nums.length;

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

// we just need to keep 2 variables, tracking money at the adjacent house and at the previous house to that one
public int robIterativeSpace(int[] nums) {
// Time Complexity : O(N), need to go through all the houses to find max wealth
// Space Complexity : O(1)
int len = nums.length;

int previous = 0;
int neighbor = 0;
for (int num : nums) {
int current = Math.max(neighbor, num + previous);
previous = neighbor;
neighbor = current;
}
return nums[len-1];
}

}