From 250a90a12e8423fc4b5b03b80a71c83aeac68c3b Mon Sep 17 00:00:00 2001 From: Paridhi Malviya Date: Sat, 10 Jan 2026 10:21:06 -0600 Subject: [PATCH] Coin change 2. Paint houses --- CoinChange2.swift | 124 +++++++++++++++++++++++++++++++++ PaintHouses.swift | 171 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 295 insertions(+) create mode 100644 CoinChange2.swift create mode 100644 PaintHouses.swift diff --git a/CoinChange2.swift b/CoinChange2.swift new file mode 100644 index 00000000..49491785 --- /dev/null +++ b/CoinChange2.swift @@ -0,0 +1,124 @@ +// +// CoinChange2.swift +// DSA-Practice +// +// Created by Paridhi Malviya on 1/5/26. +// + +class CoinChange2 { + /* + Print all the ways in which coins can be selected to form sum 11 + from coins - [2,5,1] + */ + + init() { + selectWaysToFormASum(coins: [1,2,5], amount: 5) + } + + /* + Brute force - Use exhaustive approach (recursive one). choose or not choose. + at the end of the path, either we will have amount -ve or the coins are over but amount is remaining -> invalid path + If the amount left is 0 at the end -> valid path + + Time complexity - pow(2, m+n), m -> amount, n -> no of coins , m+n - total depth of the tree. + every decision node should have coins, amoutn or index of the coin + */ + func selectWaysToFormASum(coins: [Int], amount: Int) { + let noOfWays = helper(coins: coins, amount: amount, index: 0) + print("noOfWays *** \(noOfWays)") + + let noOfWaysUsingTabulation = coinChangeUsingTabulation(amount: 5, coins: [1,2,5]) + print("noof ways using tabulation \(noOfWaysUsingTabulation)") + + let noOfWaysUsingMemoization = coinChangeUsingMemoization(amount: 5, coins: [1, 2, 5]) + print("no of ways using memoization \(noOfWaysUsingMemoization)") + } + + func helper(coins: [Int], amount: Int, index: Int) -> Int { + //base + if (amount == 0) { + //valid path + return 1 + } + //invalid path + if (amount < 0 || index == coins.count) { + return 0 + } + //not choose + let case1 = helper(coins: coins, amount: amount, index: index + 1) + //choose + let case2 = helper(coins: coins, amount: amount - coins[index], index: index) + + let totalWays = case1 + case2 + return totalWays + } + + //Using DP - Tabulation + //time complexity -> O(mn) + func coinChangeUsingTabulation(amount: Int, coins: [Int]) -> Int { + let m = coins.count + let n = amount + var dp = Array(repeating: Array(repeating: 0, count: n + 1), count: m + 1) + dp[0][0] = 1 + for i in 1...m { + dp[i][0] = 1 + for j in 1...n { + //till the time denomination of coin is smaller than amount, till that time, we have 0 cases only. + if (coins[i - 1] > j) { + dp[i][j] = dp[i-1][j] + } else { + //add up 0 cases and 1 cases + dp[i][j] = dp[i-1][j] + dp[i][j - coins[i - 1]] + } + } + } + return dp[m][n] + } + + //using top down DP - memoization + func coinChangeUsingMemoization(amount: Int, coins: [Int]) -> Int { + + let m = coins.count + let n = amount + var memo: [[Int]] = Array(repeating: Array(repeating: -1, count: n + 1), count: m + 1) + let noOfWays = helperForMemoizationApproach(amount: amount, coins: coins,index: 0, memo: &memo) + print("memo *** \(memo)") + print("o of ways using memoization \(noOfWays)") + return noOfWays + } + + func helperForMemoizationApproach(amount: Int, coins: [Int], index: Int, memo: inout [[Int]]) -> Int { + //valid path + if (amount == 0) { + return 1 + } + //invalid path + if (amount < 0 || index == coins.count) { + return 0 + } + + if (memo[index][amount] != -1) { + return memo[index][amount] + } + //not choose + let noOfWaysByExcluding = helperForMemoizationApproach(amount: amount, coins: coins, index: index + 1, memo: &memo) + //choose + let noOfWaysByIncluding = helperForMemoizationApproach(amount: amount - coins[index], coins: coins, index: index, memo: &memo) + + memo[index][amount] = noOfWaysByExcluding + noOfWaysByIncluding + return noOfWaysByExcluding + noOfWaysByIncluding + } +} +/* + to avoid 2pow n time complexity. + for 1 input variable - 1-D array + for 2 -> 2-D array + if 5 input variables. probme swill be repeating if all 5 parameters are same. + dimensinal reduction + for 3 input varibales - do a for loop for 3rd dimension choices. - burst balloons + beyond 3 input variables - it's very difficult to have repeated subproblems. And repeated subproblems are required for a problme to be a dp problmes. + //If amount - 0, coin - 1-> don't choose this coin,. SO 1 way of choosing + + never put your result into the recursion stack. In some case, it works but not a good practice + + */ diff --git a/PaintHouses.swift b/PaintHouses.swift new file mode 100644 index 00000000..560489d0 --- /dev/null +++ b/PaintHouses.swift @@ -0,0 +1,171 @@ +// +// PaintHouses.swift +// DSA-Practice +// +// Created by Paridhi Malviya on 1/5/26. +// + +/* + Approach - can't go with greedy approach because greedy gives local optimal solution. It's a high possibility that the solution that we are taking at row 1 can end up being the wrong choice. + So explore all paths. - exhaustive approach + we create tree for all elements present in row 1st + depth - no of houses + time complexity -> 2pow n(no of houses) * m(no of colors) + */ +class PaintHouses { + init() { + let minCost = minCost(costs: [[17, 2, 17], [16, 16, 5], [14, 3, 19]]) + print(" PaintHouses minCost \(minCost)") + + let minCostFromDpSolution = minCostUsingDP(costs: [[17, 2, 17], [16, 16, 5], [14, 3, 19]]) + print("min cost from dp solution \(minCostFromDpSolution)") + + let minCostFromUsingVars = minCostUsingThreeVariables(costs: [[17, 2, 17], [16, 16, 5], [14, 3, 19]]) + print("min cost by using 3 variables \(minCostFromUsingVars)") + + let minCostUsingDPMemoization = minCostUsingDPMemoization(costs: [[17, 2, 17], [16, 16, 5], [14, 3, 19]]) + print("min cost using dp memoization \(minCostUsingDPMemoization)") + } + + func minCost(costs: [[Int]]) -> Int { + + let costs = [[17, 2, 17], [16, 16, 5], [14, 3, 19]] + + let colorRMinCost = helper(costs: costs, i: 0, j: 0) + let colorBlueMinCost = helper(costs: costs, i: 0, j: 1) + let colorGreenMinCost = helper(costs: costs, i: 0, j: 2) + let minCost = min(colorRMinCost, colorBlueMinCost, colorGreenMinCost) + return minCost + } + + /* + time compelxity - O(m * 2 pow n) + */ + func helper(costs:[[Int]], i: Int, j: Int) -> Int { + //base + if (i == costs.count) { + return 0 + } + + //logic + var totalCost = 0 + if (j == 0) { + //then consider elements for j = 1 and j = 2 + let cost1 = helper(costs: costs, i: i + 1, j: 1) + let cost2 = helper(costs: costs, i: i + 1, j: 2) + totalCost = min(cost1, cost2) + costs[i][j] + } else if (j == 1) { + //then consider elements for j = 0 and j = 2 + let cost1 = helper(costs: costs, i: i + 1, j: 0) + let cost2 = helper(costs: costs, i: i + 1, j: 2) + totalCost = min(cost1, cost2) + costs[i][j] + } else if (j == 2) { + //then consider elements for j = 0 and j = 1 + let cost1 = helper(costs: costs, i: i + 1, j: 0) + let cost2 = helper(costs: costs, i: i + 1, j: 1) + totalCost = min(cost1, cost2) + costs[i][j] + } + return totalCost + } + + //Using dynamic Programming - tabulation + /* + Time complexity - O(n) + Space complexity - O(n) + */ + func minCostUsingDP(costs: [[Int]]) -> Int { + var dp: [[Int]] = Array(repeating: Array(repeating: 0, count: costs[0].count), count: costs.count) + //fill the bottom row + dp[costs.count - 1][0] = costs[costs.count - 1][0] + dp[costs.count - 1][1] = costs[costs.count - 1][1] + dp[costs.count - 1][2] = costs[costs.count - 1][2] + + //loop from bottom + for i in stride(from: costs.count - 2, through: 0, by: -1) { + dp[i][0] = costs[i][0] + min(dp[i + 1][1], dp[i + 1][2]) + dp[i][1] = costs[i][1] + min(dp[i + 1][0], dp[i + 1][2]) + dp[i][2] = costs[i][2] + min(dp[i + 1][0], dp[i + 1][1]) + } + return min(dp[0][0], min(dp[0][1], dp[0][2])) + } + + + //Can convert this matrix to a 1-D variable. + //Since it's just 3 columns, we can use 3 variables as well. + + // time complexity - O(n), Space complexity - O(1) + func minCostUsingThreeVariables(costs: [[Int]]) -> Int { + var costRed = costs[costs.count - 1][0] + var costBlue = costs[costs.count - 1][1] + var costGreen = costs[costs.count - 1][2] + + for i in stride(from: costs.count - 2, through: 0, by: -1) { + let tempRed = costRed + let tempBlue = costBlue + + costRed = costs[i][0] + min(costBlue, costGreen) + costBlue = costs[i][1] + min(tempRed, costGreen) + costGreen = costs[i][2] + min(tempRed, tempBlue) + } + return min(costRed, min(costBlue, costGreen)) + } + + + /* + Path tracking -> + approach 1 -as we go back, keep track of the elements which we selected in bottom rows. But for 10000 rows, lots of space will be wasted to keep track of path consisting of 10000 elements i each column. + btter approach -> keep track of the previous element in previous row by using the column index. Storing only 1 int (next column index from where we are coming back) at each places. + We can keep a path tracking matrix as well. + */ +} + +extension PaintHouses { + + //using DP - Memoization + func minCostUsingDPMemoization(costs: [[Int]]) -> Int { + let costs = [[17, 2, 17], [16, 16, 5], [14, 3, 19]] + + var memo: [[Int]] = Array(repeating: Array(repeating: -1, count: 3), count: 3) + + let colorMinRed = helperForNMemoization(costs: costs, i: 0, j: 0, memo: &memo) + let colorMinBlue = helperForNMemoization(costs: costs, i: 0, j: 1, memo: &memo) + let colorMinGreen = helperForNMemoization(costs: costs, i: 0, j: 2, memo: &memo) + + return min(colorMinRed, min(colorMinBlue, colorMinGreen)) + } + + /* + time compelxity - O(nm) + */ + func helperForNMemoization(costs:[[Int]], i: Int, j: Int, memo: inout [[Int]]) -> Int { + //base + if (i == costs.count) { + return 0 + } + + if (memo[i][j] != -1) { + return memo[i][j] + } + + //logic + var totalCost = 0 + if (j == 0) { + //then consider elements for j = 1 and j = 2 + let cost1 = helper(costs: costs, i: i + 1, j: 1) + let cost2 = helper(costs: costs, i: i + 1, j: 2) + totalCost = min(cost1, cost2) + costs[i][j] + } else if (j == 1) { + //then consider elements for j = 0 and j = 2 + let cost1 = helper(costs: costs, i: i + 1, j: 0) + let cost2 = helper(costs: costs, i: i + 1, j: 2) + totalCost = min(cost1, cost2) + costs[i][j] + } else if (j == 2) { + //then consider elements for j = 0 and j = 1 + let cost1 = helper(costs: costs, i: i + 1, j: 0) + let cost2 = helper(costs: costs, i: i + 1, j: 1) + totalCost = min(cost1, cost2) + costs[i][j] + } + memo[i][j] = totalCost + return totalCost + } +}