Əsas məzmuna keçin

Coin Change (Sikkə Dəyişmə)

Problem Təsviri​

Müxtəlif nominal dəyərli sikkələriniz var və müəyyən bir məbləğ əldə etməlisiniz. Minimum neçə sikkə istifadə etməlisiniz? Hər sikkə növündən istədiyiniz qədər istifadə edə bilərsiniz.

Məsələn:

  • Sikkələr: [1, 2, 5], Məbləğ: 11

    • Cavab: 3 sikkə (5 + 5 + 1)
  • Sikkələr: [2], Məbləğ: 3

    • Cavab: -1 (mümkün deyil)
  • Sikkələr: [1, 3, 4], Məbləğ: 6

    • Cavab: 2 sikkə (3 + 3)

Recurrence Relation:

dp[amount] = min(dp[amount - coin] + 1) for all coins

Müxtəlif Yanaşmalar​

1. Sadə Rekursiya (Naive Approach)​

Koda bax
public class CoinChangeNaive {

public static int coinChange(int[] coins, int amount) {
// Base cases
if (amount == 0) {
return 0;
}
if (amount < 0) {
return -1;
}

int minCoins = Integer.MAX_VALUE;

// Try using each coin
for (int coin : coins) {
int result = coinChange(coins, amount - coin);

// If valid solution found
if (result >= 0 && result < minCoins) {
minCoins = result + 1;
}
}

return minCoins == Integer.MAX_VALUE ? -1 : minCoins;
}

public static void main(String[] args) {
System.out.println("Coin Change (Naive Recursion):");

int[] coins1 = {1, 2, 5};
System.out.println("Coins: [1, 2, 5], Amount: 11");
System.out.println("Minimum coins: " + coinChange(coins1, 11));

int[] coins2 = {2};
System.out.println("\nCoins: [2], Amount: 3");
System.out.println("Minimum coins: " + coinChange(coins2, 3));

int[] coins3 = {1, 3, 4};
System.out.println("\nCoins: [1, 3, 4], Amount: 6");
System.out.println("Minimum coins: " + coinChange(coins3, 6));
}
}

Mürəkkəblik:

  • Time Complexity: O(S^n) - S: məbləğ, n: sikkə sayı (Eksponensial)
  • Space Complexity: O(n) - Rekursiya stack-i

Problemlər:

  • Çox yavaş böyük məbləğlər üçün
  • Eyni alt-problemlər dəfələrlə hesablanır

2. Memoization (Top-Down DP)​

Koda bax
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

public class CoinChangeMemoization {

// Using HashMap for memoization
private static Map<Integer, Integer> memo;
private static int[] coins;

public static int coinChange(int[] coinArray, int amount) {
coins = coinArray;
memo = new HashMap<>();

int result = coinChangeHelper(amount);
return result == Integer.MAX_VALUE ? -1 : result;
}

private static int coinChangeHelper(int amount) {
// Base cases
if (amount == 0) {
return 0;
}
if (amount < 0) {
return Integer.MAX_VALUE;
}

// Check if already computed
if (memo.containsKey(amount)) {
return memo.get(amount);
}

int minCoins = Integer.MAX_VALUE;

// Try each coin
for (int coin : coins) {
int result = coinChangeHelper(amount - coin);
if (result != Integer.MAX_VALUE) {
minCoins = Math.min(minCoins, result + 1);
}
}

memo.put(amount, minCoins);
return minCoins;
}

// Alternative implementation with array
public static int coinChangeArray(int[] coins, int amount) {
int[] memo = new int[amount + 1];
Arrays.fill(memo, -2); // -2: not computed, -1: impossible

int result = coinChangeHelperArray(coins, amount, memo);
return result;
}

private static int coinChangeHelperArray(int[] coins, int amount, int[] memo) {
if (amount == 0) return 0;
if (amount < 0) return -1;

if (memo[amount] != -2) {
return memo[amount];
}

int minCoins = Integer.MAX_VALUE;

for (int coin : coins) {
int result = coinChangeHelperArray(coins, amount - coin, memo);
if (result >= 0 && result < minCoins) {
minCoins = result + 1;
}
}

memo[amount] = (minCoins == Integer.MAX_VALUE) ? -1 : minCoins;
return memo[amount];
}

public static void main(String[] args) {
System.out.println("Coin Change (Memoization):");

int[] coins1 = {1, 2, 5};
System.out.println("Coins: [1, 2, 5], Amount: 11");
System.out.println("Minimum coins: " + coinChange(coins1, 11));

int[] coins2 = {2};
System.out.println("\nCoins: [2], Amount: 3");
System.out.println("Minimum coins: " + coinChange(coins2, 3));

int[] coins3 = {186, 419, 83, 408};
System.out.println("\nCoins: [186, 419, 83, 408], Amount: 6249");
long startTime = System.currentTimeMillis();
System.out.println("Minimum coins: " + coinChange(coins3, 6249));
long endTime = System.currentTimeMillis();
System.out.println("Vaxt: " + (endTime - startTime) + " ms");
}
}

Mürəkkəblik:

  • Time Complexity: O(S * n) - S: məbləğ, n: sikkə sayı
  • Space Complexity: O(S) - Memo table və rekursiya stack-i

3. Tabulation (Bottom-Up DP)​

Koda bax
import java.util.Arrays;

public class CoinChangeTabulation {

public static int coinChange(int[] coins, int amount) {
// Create DP table
int[] dp = new int[amount + 1];

// Initialize with maximum value (impossible)
Arrays.fill(dp, amount + 1);

// Base case: 0 amount needs 0 coins
dp[0] = 0;

// Fill table bottom-up
for (int i = 1; i <= amount; i++) {
for (int coin : coins) {
if (coin <= i) {
dp[i] = Math.min(dp[i], dp[i - coin] + 1);
}
}
}

// If dp[amount] is still amount + 1, it's impossible
return dp[amount] > amount ? -1 : dp[amount];
}

// Alternative: coin-first approach
public static int coinChangeCoinFirst(int[] coins, int amount) {
int[] dp = new int[amount + 1];
Arrays.fill(dp, Integer.MAX_VALUE);
dp[0] = 0;

// For each coin
for (int coin : coins) {
// Update all amounts that can be made with this coin
for (int i = coin; i <= amount; i++) {
if (dp[i - coin] != Integer.MAX_VALUE) {
dp[i] = Math.min(dp[i], dp[i - coin] + 1);
}
}
}

return dp[amount] == Integer.MAX_VALUE ? -1 : dp[amount];
}

// Method to show DP table
public static void showDPTable(int[] coins, int amount) {
int[] dp = new int[amount + 1];
Arrays.fill(dp, amount + 1);
dp[0] = 0;

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

System.out.println("DP Table for coins " + Arrays.toString(coins) + ":");
System.out.print("Amount: ");
for (int i = 0; i <= amount; i++) {
System.out.printf("%4d", i);
}
System.out.println();
System.out.print("Coins: ");
for (int i = 0; i <= amount; i++) {
if (dp[i] > amount) {
System.out.print(" -1");
} else {
System.out.printf("%4d", dp[i]);
}
}
System.out.println("\n");
}

public static void main(String[] args) {
System.out.println("Coin Change (Tabulation):");

int[] coins1 = {1, 2, 5};
System.out.println("Coins: [1, 2, 5], Amount: 11");
System.out.println("Minimum coins: " + coinChange(coins1, 11));

showDPTable(coins1, 11);

int[] coins2 = {1, 3, 4};
System.out.println("Coins: [1, 3, 4], Amount: 6");
System.out.println("Minimum coins: " + coinChange(coins2, 6));

// Performance test
long startTime = System.currentTimeMillis();
int result = coinChange(coins1, 10000);
long endTime = System.currentTimeMillis();

System.out.println("\nAmount: 10000, Minimum coins: " + result);
System.out.println("Vaxt: " + (endTime - startTime) + " ms");
}
}

Mürəkkəblik:

  • Time Complexity: O(S * n) - S: məbləğ, n: sikkə sayı
  • Space Complexity: O(S) - DP table üçün

4. Path Reconstruction (Konkret Həll)​

Koda bax
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class CoinChangeWithPath {

public static class Result {
int minCoins;
List<Integer> coins;

Result(int minCoins, List<Integer> coins) {
this.minCoins = minCoins;
this.coins = coins;
}
}

public static Result coinChangeWithCoins(int[] coins, int amount) {
// DP table
int[] dp = new int[amount + 1];
Arrays.fill(dp, amount + 1);
dp[0] = 0;

// Parent array to track which coin was used
int[] parent = new int[amount + 1];
Arrays.fill(parent, -1);

// Fill DP table
for (int i = 1; i <= amount; i++) {
for (int coin : coins) {
if (coin <= i && dp[i - coin] + 1 < dp[i]) {
dp[i] = dp[i - coin] + 1;
parent[i] = coin;
}
}
}

// If impossible
if (dp[amount] > amount) {
return new Result(-1, new ArrayList<>());
}

// Reconstruct path
List<Integer> usedCoins = new ArrayList<>();
int currentAmount = amount;

while (currentAmount > 0) {
int coinUsed = parent[currentAmount];
usedCoins.add(coinUsed);
currentAmount -= coinUsed;
}

return new Result(dp[amount], usedCoins);
}

public static void main(String[] args) {
System.out.println("Coin Change with Path Reconstruction:");

int[] coins1 = {1, 2, 5};
int amount1 = 11;
Result result1 = coinChangeWithCoins(coins1, amount1);

System.out.println("Coins: [1, 2, 5], Amount: 11");
System.out.println("Minimum coins: " + result1.minCoins);
System.out.println("Coins used: " + result1.coins);

int[] coins2 = {1, 3, 4};
int amount2 = 6;
Result result2 = coinChangeWithCoins(coins2, amount2);

System.out.println("\nCoins: [1, 3, 4], Amount: 6");
System.out.println("Minimum coins: " + result2.minCoins);
System.out.println("Coins used: " + result2.coins);
}
}

Mürəkkəblik:

  • Time Complexity: O(S * n)
  • Space Complexity: O(S) - DP və parent array-ləri

5. Coin Change 2 (Kombinasiya Sayı)​

Koda bax
import java.util.Arrays;

public class CoinChange2 {

// LeetCode 518: Coin Change 2
// Verilmiş məbləği əldə etmək üçün neçə fərqli yol var?

public static int change(int amount, int[] coins) {
// dp[i] = i məbləğini əldə etmək üçün yolların sayı
int[] dp = new int[amount + 1];
dp[0] = 1; // 0 məbləğini əldə etməyin 1 yolu var (heç bir sikkə istifadə etməmək)

// Hər sikkə üçün
for (int coin : coins) {
// Bütün məbləğləri yenilə
for (int i = coin; i <= amount; i++) {
dp[i] += dp[i - coin];
}
}

return dp[amount];
}

// With 2D DP (more intuitive)
public static int change2D(int amount, int[] coins) {
int n = coins.length;
int[][] dp = new int[n + 1][amount + 1];

// Base case: 0 məbləğ üçün 1 yol var
for (int i = 0; i <= n; i++) {
dp[i][0] = 1;
}

for (int i = 1; i <= n; i++) {
for (int j = 1; j <= amount; j++) {
// Don't use current coin
dp[i][j] = dp[i - 1][j];

// Use current coin (if possible)
if (coins[i - 1] <= j) {
dp[i][j] += dp[i][j - coins[i - 1]];
}
}
}

return dp[n][amount];
}

public static void main(String[] args) {
System.out.println("Coin Change 2 (Combination Count):");

int[] coins1 = {1, 2, 5};
System.out.println("Coins: [1, 2, 5], Amount: 5");
System.out.println("Number of ways: " + change(5, coins1));

int[] coins2 = {2};
System.out.println("\nCoins: [2], Amount: 3");
System.out.println("Number of ways: " + change(3, coins2));

int[] coins3 = {1, 2, 5};
System.out.println("\nCoins: [1, 2, 5], Amount: 11");
System.out.println("Number of ways: " + change(11, coins3));
}
}

Mürəkkəblik:

  • Time Complexity: O(S * n)
  • Space Complexity: O(S) - 1D DP

Performance Müqayisəsi​

Koda bax
public class CoinChangeComparison {

public static void comparePerformance() {
int[] coins = {1, 2, 5};
int[] amounts = {10, 20, 50, 100, 500};

System.out.println("Performance Müqayisəsi:");
System.out.println("Amount\tNaive\t\tMemo\t\tTabulation");

for (int amount : amounts) {
System.out.print(amount + "\t");

// Naive (only for small amounts)
if (amount <= 20) {
long start = System.nanoTime();
CoinChangeNaive.coinChange(coins, amount);
long end = System.nanoTime();
System.out.print((end - start) / 1000 + "μs\t\t");
} else {
System.out.print("Too slow\t");
}

// Memoization
long start = System.nanoTime();
CoinChangeMemoization.coinChange(coins, amount);
long end = System.nanoTime();
System.out.print((end - start) / 1000 + "μs\t\t");

// Tabulation
start = System.nanoTime();
CoinChangeTabulation.coinChange(coins, amount);
end = System.nanoTime();
System.out.print((end - start) / 1000 + "μs");

System.out.println();
}
}

public static void main(String[] args) {
comparePerformance();
}
}

Müsahibə Sualları​

1. Minimum Sikkə Sayı​

Sual: Verilmiş məbləği əldə etmək üçün minimum sikkə sayını tapın. LeetCode: Problem 322 - Coin Change

2. Kombinasiya Sayı​

Sual: Verilmiş məbləği əldə etmək üçün neçə fərqli kombinasiya var? LeetCode: Problem 518 - Coin Change 2

3. Konkret Həll​

Sual: Yalnız minimum sayı deyil, hansı sikkələrin istifadə edildiyini göstərin. Cavab: Parent array ilə path reconstruction

4. Məhdud Sikkələr​

Sual: Hər sikkədən yalnız müəyyən sayda istifadə edə bilərsinizsə? Cavab: 2D DP - dp[i][j] = coin i istifadə edərək j məbləği

5. Maksimum/Minimum Kombinasiya​

Sual: Minimum və ya maksimum sayda sikkə istifadə edərək kombinasiya sayını tapın. Cavab: DP-də həm count, həm də ways saxlamaq

LeetCode Problemləri​

  1. 322. Coin Change - https://leetcode.com/problems/coin-change/ (Minimum sikkə sayı)

  2. 518. Coin Change 2 - https://leetcode.com/problems/coin-change-2/ (Kombinasiya sayı)

  3. 377. Combination Sum IV - https://leetcode.com/problems/combination-sum-iv/ (Permutasiya sayı)

  4. 279. Perfect Squares - https://leetcode.com/problems/perfect-squares/ (Oxşar pattern)

  5. 983. Minimum Cost For Tickets - https://leetcode.com/problems/minimum-cost-for-tickets/ (Variation of coin change)

Real-World Tətbiqləri​

  1. Financial Systems: Pul vermə maşınlarında optimal sikkə/əskinas bölgüsü
  2. Currency Exchange: Valyuta mübadiləsində optimal əməliyyat sayı
  3. Resource Management: Məhdud resurslarla məqsədə çatma
  4. Game Development: Oyun içi valyuta sistemi optimizasiyası
  5. Inventory Management: Müxtəlif ölçülü paketlərlə sifariş doldurma

Optimizasiya Məsləhətləri​

  1. Sikkələri sıralamaq: Bəzən erkən dayandırma imkanı verir
  2. Greedy olmur: Greedy approach (ən böyük sikkədən başlamaq) həmişə işləmir
  3. Space optimization: 1D DP kifayətdir, 2D lazım deyil
  4. Early termination: dp[amount] məbləğdən böyükdürsə, impossible
  5. Coin-first vs Amount-first: Problem növündən asılı olaraq seçmək

Əlaqəli Problemlər​

  • Unbounded Knapsack
  • Partition Equal Subset Sum
  • Target Sum
  • Combination Sum
  • Perfect Squares