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
124 changes: 124 additions & 0 deletions CoinChange2.swift
Original file line number Diff line number Diff line change
@@ -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. <dp with permutations> - 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

*/
171 changes: 171 additions & 0 deletions PaintHouses.swift
Original file line number Diff line number Diff line change
@@ -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
}
}