diff --git a/CoinChange2.java b/CoinChange2.java new file mode 100644 index 00000000..718b877b --- /dev/null +++ b/CoinChange2.java @@ -0,0 +1,140 @@ +// Did this code successfully run on Leetcode : Yes +// Any problem you faced while coding this : No +// Approach : + +// We divide the problem to find out which combination of coins([C1], [C1, C2], .... [C1, CN]) can add up to given amount. +// So, we can choose a coin, how many ever times it is needed to make an amount, but once it is selected we will not +// select that coin again and will move to find the combination with the next coin. So the number of ways will, +// C = Combinations(starting with C1) + Combinations(starting with C2)..... Combinations(starting with CN) +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class CoinChange2 { + + public int change() { + int amount = 5; + int[] coins = {1,2,5}; + + // int numberOfWays = changeRecursive(amount, coins, 0); + // int numberOfWays = changeMemo(amount, coins, 0, new HashMap<>()); + // int numberOfWays = changeTabulation(amount, coins); + return changeTabulation1D(amount, coins); + } + + public int changeTabulation1D(int amount, int[] coins){ + // N: number of values in coins, A: amount + // Time Complexity : O(NA), Will need to go through all amount values for each coin; + // Space Complexity : O(A) + int rows = coins.length+1; + int cols = amount+1; + int[] table = new int[cols]; + + for (int i = 1; i < rows; i++) { + int currentCoin = coins[i-1]; + // indicates to make sum 0 using any coin[i], there is at least 1 way, that is by skipping the coin + table[0] = 1; + for (int j = 1; j < cols; j++) { + if(currentCoin <= j){ + //Save the number to make the amount j using current coin and using the previous coin(value already saved at + // at current cell) + table[j] = table[j-currentCoin] + table[j]; + } + } + } + + return table[cols-1]; + } + + + private int changeTabulation(int amount, int[] coins){ + // N: number of values in coins, A: amount + // Time Complexity : O(NA), Will need to go through matrix of size NxA; + // Space Complexity : O(NA) + int rows = coins.length+1; + int cols = amount+1; + + int[][] table = new int[rows][cols]; + for (int i = 0; i < rows; i++) { + // indicates to make sum 0 using any coin[i], there is at least 1 way, that is by skipping the coin + table[i][0] = 1; + } + + // Go through each row, calculating number of ways to create amount j using coin[i] + for(int i = 1; i < rows; i++){ + int currentCoin = coins[i-1]; + for(int j = 1; j < cols; j++){ + int include = 0; + if(currentCoin <= j){ + // Give me number of ways to count the rest of value (amount-coin[i]) using current coin itself + include = table[i][j-currentCoin]; + } + // dont include the current coin, get number of ways from previous coins to create j amount + int exclude = table[i-1][j]; + table[i][j] = include + exclude; + } + } + return table[rows-1][cols-1]; + } + + private int changeMemo(int amount, int[] coins, int index, HashMap, Integer> memo) { + // N: number of values in coins, A: amount + // Time Complexity : O(2(N+A)) = ~ O(N+A), fetches already calculated subproblem from memo + // Space Complexity : O(N+A), max depth of recursion calls, so N+A calls on the stack frame. + // Also, similar amount of values in the memo hashmap + + if(amount == 0) return 1; + if(amount < 0) return 0; + if(index >= coins.length) return 0; + + List key = List.of(amount, index); + if(memo.containsKey(key)) return memo.get(key); + + int include = changeMemo(amount-coins[index],coins, index, memo); + int exclude = changeMemo(amount, coins, index+1, memo); + int numberOfWays = include + exclude; + memo.put(key, numberOfWays); + return numberOfWays; + } + + private int changeRecursive(int amount, int[] coins, int index) { + // N: number of values in coins, A: amount + // Time Complexity : O(2^(N+ A)), at every subproblem call, we make decision, whether to take that coin or skip that coin + // for example, we can take the coin A from 0 to multiple times, till it reaches the amount. So N coins each can be taken till "amount" times + // Space Complexity : O(N+A) + // Since we are not going to use the same coin again, only first case will occur, 2nd and 3rd combination would not. + // 5 : 1+1+1+2... + // 5 : 1+2+1+1 + // 5 : 2+1+1+1 + if (amount < 0) return 0; + if (amount == 0) return 1; + if (index >= coins.length) return 0; + + // take this coin till it makes the amount and never take it again later. + int include = changeRecursive(amount - coins[index], coins, index); + + // skip the coin + int exclude = changeRecursive(amount, coins, index+1); + return include + exclude; + } + + // Added for personal notes + public int changeRecursiveIncorrect(int amount, int[] coins){ + // If we use an approach that we used in Coin change 1, it would cause duplicates like, so incorrect number of ways + // 5 = 1+1+1+2 + // 5 = 2+1+1+1 + if(amount < 0) return 0; + if(amount == 0) return 1; + + int numberOfWays = 0; + for(int coin: coins){ + if(coin <= amount){ + numberOfWays += changeRecursiveIncorrect(amount-coin, coins); + } + } + + return numberOfWays; + } + + +} diff --git a/PaintHouse.java b/PaintHouse.java new file mode 100644 index 00000000..46ec7b93 --- /dev/null +++ b/PaintHouse.java @@ -0,0 +1,134 @@ +// Did this code successfully run on Leetcode : Don't have premium account +// Any problem you faced while coding this : No +// Approach : + +// Since we cant have two houses of the same color, we need to track the current color chosen and pass it to the recursive calls. For example, if blue is chosen, we will +// try to find minimum if we chose red or green for the next house. And then add it to the blue paint cost. At any node,in the above manner, we will calculate what is cost +// of painting the house with the three colors and find their minimum value. +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class PaintHouse { + + static void main(String[] args) { + // int[][] costs = {{17,2,17}, {16, 16, 5}, {14,3,9}}; + int[][] costs = {{7, 7, 7}, {6, 6, 6}, {4, 4, 9}}; + + int withRedPaint = minPaintCost(costs, 0, 0); + int withBluePaint = minPaintCost(costs, 0, 1); + int withGreenPaint = minPaintCost(costs, 0, 2); + + int minPaintCost = Math.min(withRedPaint, Math.min(withGreenPaint, withBluePaint)); + System.out.println("Paint cost with Recursion: "+ minPaintCost); + + int withRedPaintMemo = minPaintCostMemo(costs, 0, 0, new HashMap<>()); + int withBluePaintMemo = minPaintCostMemo(costs, 0, 1, new HashMap<>()); + int withGreenPaintMemo = minPaintCostMemo(costs, 0, 2, new HashMap<>()); + int minPaintCostMemo = Math.min(withRedPaintMemo, Math.min(withBluePaintMemo, withGreenPaintMemo)); + System.out.println("Paint cost with Memo: "+ minPaintCostMemo); + + int minTabulation = minPaintCostTabulation(costs); + System.out.println("Paint cost with Tabulation: "+minTabulation); + + int minSpace = minPaintCostSpaceOptimization(costs); + System.out.println("Paint cost with Tabulation and space optimization: " + minSpace); + } + + public static int minPaintCostSpaceOptimization(int[][] costs) { + // Time Complexity : O(N), where N is number of houses + // Space Complexity : O(1), use 3 variables to keep track of what is current min if we choose color Red, Green or Blue + int minRed = 0, minBlue = 0, minGreen = 0; + + for (int[] cost : costs) { + int prevRed = minRed; + int prevBlue = minBlue; + int prevGreen = minGreen; + + // Calculate value at each house if the current color is chosen, then add it with the minimum cost of painting the previous house with other 2 colors + // as we cant have two adjacent houses painted with same color + minRed = cost[0] + Math.min(prevBlue, prevGreen); + minBlue = cost[1] + Math.min(prevRed, prevGreen); + minGreen = cost[2] + Math.min(prevRed, prevBlue); + } + + return Math.min(minRed, Math.min(minBlue, minGreen)); + } + + public static int minPaintCostTabulation(int[][] costs) { + // Time Complexity : O(NC), where N is number of houses, C is number of colors + // Space Complexity : O(NC), store value at each house by adding the current chosen color with minimum of other two colors for the previous house + int houses = costs.length+1; + int colors = costs[0].length; + int[][] minCost = new int[houses][colors]; + // The first row is all 0s, so that house 1 can use this result + + for(int i = 1; i < houses; i++){ + for (int j = 0; j < colors; j++) { + if(j == 0) { + // current color cost + min of colors for prev house + minCost[i][j] = costs[i-1][0] + Math.min(minCost[i-1][1], minCost[i-1][2]); + }else if(j == 1){ + minCost[i][j] = costs[i-1][1] + Math.min(minCost[i-1][0], minCost[i-1][2]); + }else { + minCost[i][j] = costs[i-1][2] + Math.min(minCost[i-1][0], minCost[i-1][1]); + } + } + } + // The last row has all the costs for different colors added up, so find the min of the three. + return Math.min(minCost[houses-1][0], Math.min(minCost[houses-1][1], minCost[houses-1][2])); + } + + public static int minPaintCostMemo(int[][] costs, int houseIdx, int currentColor, Map, Integer> memo) { + // Time Complexity : O(NC), where N is number of houses, C is number of colors + // Space Complexity : O(N), the recursion stack will be deep till it evaluates all houses. + + if (houseIdx >= costs.length) return 0; + + // Keep the changing house index and color in the memo + List key = List.of(houseIdx, currentColor); + if (memo.containsKey(key)) { + return memo.get(key); + } + + int currentCost = costs[houseIdx][currentColor]; + if (currentColor == 0) { + currentCost += Math.min(minPaintCostMemo(costs, houseIdx + 1, 1, memo), minPaintCostMemo(costs, houseIdx + 1, 2, memo)); + } + + + if (currentColor == 1) { + currentCost += Math.min(minPaintCostMemo(costs, houseIdx + 1, 0, memo), minPaintCostMemo(costs, houseIdx + 1, 2, memo)); + } + + if (currentColor == 2) { + currentCost += Math.min(minPaintCostMemo(costs, houseIdx + 1, 0, memo), minPaintCostMemo(costs, houseIdx + 1, 1, memo)); + } + + memo.put(key, currentCost); + return currentCost; + } + + public static int minPaintCost(int[][] costs, int houseIdx, int currentColor) { + // Time Complexity : O(3(2^N)). The recursion levels will have 3, 6, 12 calls, till N houses are evaluated + // Space Complexity : O(N), the recursion stack will be deep till it evaluates all N houses. + if (houseIdx >= costs.length) return 0; + + int currentCost = costs[houseIdx][currentColor]; + + // If we take any current color. Also find the min cost if we chose any of other 2 color for the next house recursively. + if (currentColor == 0) { + currentCost += Math.min(minPaintCost(costs, houseIdx + 1, 1), minPaintCost(costs, houseIdx + 1, 2)); + } + + if (currentColor == 1) { + currentCost += Math.min(minPaintCost(costs, houseIdx + 1, 0), minPaintCost(costs, houseIdx + 1, 2)); + } + + if (currentColor == 2) { + currentCost += Math.min(minPaintCost(costs, houseIdx + 1, 0), minPaintCost(costs, houseIdx + 1, 1)); + } + + return currentCost; + } +}