Skip to content

Latest commit

 

History

History
 
 

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 

README.md

🧠 Dynamic Programming (DP)

Catatan: Folder ini berisi implementasi algoritma-algoritma Dynamic Programming yang populer dan sering digunakan.


📚 Daftar Isi

  1. Apa itu Dynamic Programming?
  2. Memoization vs Tabulation
  3. Karakteristik Masalah DP
  4. Algoritma yang Tersedia
  5. Resume Bahasa Bayi

Apa itu Dynamic Programming?

Dynamic Programming (DP) adalah teknik optimasi dalam pemrograman untuk menyelesaikan masalah kompleks dengan cara memecahnya menjadi sub-masalah yang lebih kecil dan menyimpan hasil sub-masalah agar tidak perlu dihitung ulang.

🎯 Analogi Sederhana

Bayangkan kamu sedang membuat kue dan butuh mengocok telur:

  • Tanpa DP: Setiap kali butuh telur kocok, kamu mengocok dari awal lagi dan lagi 😫
  • Dengan DP: Kamu mengocok sekali, simpan hasilnya, lalu gunakan berkali-kali 😊

💡 Prinsip Dasar DP

┌─────────────────────────────────────────────────────────────┐
│  "Jangan menghitung sesuatu yang sudah pernah dihitung!"    │
└─────────────────────────────────────────────────────────────┘

Memoization vs Tabulation

DP memiliki dua pendekatan utama:

1. Memoization (Top-Down) ⬇️

  • Mulai dari masalah besar, pecah menjadi kecil
  • Menyimpan hasil di cache/memory
  • Menggunakan rekursi
// Contoh: Fibonacci dengan Memoization
const fib = (n: number, memo: Record<number, number> = {}): number => {
  if (n <= 1) return n;
  if (memo[n]) return memo[n];  // Ambil dari cache!
  
  memo[n] = fib(n - 1, memo) + fib(n - 2, memo);  // Simpan ke cache
  return memo[n];
};

Visualisasi:

fib(5)
├── fib(4) [hitung & simpan]
│   ├── fib(3) [hitung & simpan]
│   │   ├── fib(2) [hitung & simpan]
│   │   └── fib(1) [return 1]
│   └── fib(2) [ambil dari cache! ✓]
└── fib(3) [ambil dari cache! ✓]

2. Tabulation (Bottom-Up) ⬆️

  • Mulai dari kasus terkecil, bangun ke atas
  • Menggunakan iterasi dengan tabel/array
  • Lebih efisien dalam penggunaan memory
// Contoh: Fibonacci dengan Tabulation
const fib = (n: number): number => {
  if (n <= 1) return n;
  
  const dp: number[] = [0, 1];  // Tabel untuk menyimpan hasil
  
  for (let i = 2; i <= n; i++) {
    dp[i] = dp[i - 1] + dp[i - 2];  // Isi tabel dari bawah
  }
  
  return dp[n];
};

Visualisasi Tabel:

Index:  0   1   2   3   4   5
Value: [0] [1] [1] [2] [3] [5]
              ↑   ↑   ↑   ↑
            0+1 1+1 1+2 2+3

📊 Perbandingan

Aspek Memoization Tabulation
Arah Top-Down Bottom-Up
Implementasi Rekursi Iterasi
Memory Stack + Cache Tabel array
Kecepatan Bisa lebih lambat Biasanya lebih cepat
Kasus yang tidak terpakai Mungkin dihitung Semua dihitung

Karakteristik Masalah DP

Bagaimana mengetahui suatu masalah bisa diselesaikan dengan DP?

✅ Ciri-ciri Masalah DP:

  1. Optimal Substructure (Sub-struktur Optimal)

    • Solusi optimal masalah besar terdiri dari solusi optimal sub-masalah
    • Contoh: Rute terpendek A→C melalui B = Rute terpendek A→B + Rute terpendek B→C
  2. Overlapping Subproblems (Sub-masalah Tumpang Tindih)

    • Sub-masalah yang sama dihitung berkali-kali
    • Contoh: Dalam fibonacci, fib(3) dipanggil berkali-kali
Masalah: fib(5)
         /    \
     fib(4)  fib(3)    ← fib(3) muncul di sini!
      /  \      /  \
  fib(3) fib(2) ...  ← fib(3) muncul lagi!
   /  \
  ... fib(2)         ← fib(2) juga muncul berkali-kali!

🧪 Cara Identifikasi

Tanyakan pada dirimu:

  1. "Apakah saya bisa memecah ini menjadi bagian lebih kecil?"
  2. "Apakah ada perhitungan yang berulang?"
  3. "Apakah solusi optimal bergantung pada solusi optimal bagian kecil?"

Jika jawabannya YA untuk semua → Gunakan DP! 🎉


Algoritma yang Tersedia

1. Knapsack Problem (0/1)

File: knapsack.ts

📖 Penjelasan

Kamu punya tas dengan kapasitas terbatas dan beberapa barang dengan berat dan nilai berbeda. Pilih barang mana yang akan dimasukkan agar nilai total maksimal tanpa melebihi kapasitas tas!

"0/1" artinya: Setiap barang hanya bisa diambil 0 kali (tidak diambil) atau 1 kali (diambil), tidak bisa diambil sebagian.

🎯 Contoh Kasus

Kapasitas Tas: 8 kg

Barang:
┌─────────┬────────┬────────┐
│ Barang  │ Berat  │ Nilai  │
├─────────┼────────┼────────┤
│    A    │  3 kg  │  $30   │
│    B    │  4 kg  │  $50   │
│    C    │  5 kg  │  $60   │
└─────────┴────────┴────────┘

Solusi Optimal: Ambil A + B = 3 + 4 = 7 kg, Nilai = 30 + 50 = $80
                Atau hanya C = 5 kg, Nilai = $60
                
Jawaban: $80 (A + B)

🧮 Visualisasi Tabel DP

Tabel DP[i][w] = Nilai maksimal menggunakan barang 1..i dengan kapasitas w

        Kapasitas →
         0   1   2   3   4   5   6   7   8
Barang  ┌───┬───┬───┬───┬───┬───┬───┬───┬───┐
  0     │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │  ← Tidak ada barang
        ├───┼───┼───┼───┼───┼───┼───┼───┼───┤
  A(3)  │ 0 │ 0 │ 0 │30 │30 │30 │30 │30 │30 │  ← Bisa ambil A
        ├───┼───┼───┼───┼───┼───┼───┼───┼───┤
  B(4)  │ 0 │ 0 │ 0 │30 │50 │50 │50 │80 │80 │  ← Bisa ambil A+B
        ├───┼───┼───┼───┼───┼───┼───┼───┼───┤
  C(5)  │ 0 │ 0 │ 0 │30 │50 │60 │60 │80 │90 │  ← Max(A+B, C)=90
        └───┴───┴───┴───┴───┴───┴───┴───┴───┘
                                          ↑
                                    Jawaban: 90

💻 Implementasi

export const knapsack = (
  capacity: number,
  weights: number[],
  values: number[]
): number => {
  const n = weights.length;
  
  // Buat tabel DP dengan ukuran (n+1) x (capacity+1)
  const dp: number[][] = Array(n + 1)
    .fill(0)
    .map(() => Array(capacity + 1).fill(0));

  // Isi tabel dari barang 1 sampai n
  for (let i = 1; i <= n; i++) {
    const w = weights[i - 1];   // Berat barang ke-i
    const v = values[i - 1];    // Nilai barang ke-i
    
    for (let c = 1; c <= capacity; c++) {
      if (w <= c) {
        // Pilih maksimal antara:
        // 1. Ambil barang ini: v + dp[i-1][c-w]
        // 2. Tidak ambil: dp[i-1][c]
        dp[i][c] = Math.max(v + dp[i - 1][c - w], dp[i - 1][c]);
      } else {
        // Berat terlalu berat, tidak bisa diambil
        dp[i][c] = dp[i - 1][c];
      }
    }
  }

  return dp[n][capacity];
};

🔑 Formula DP

DP[i][w] = {
    0                                           jika i = 0 atau w = 0
    max(value[i] + DP[i-1][w-weight[i]],         jika weight[i] <= w
        DP[i-1][w])
    DP[i-1][w]                                  jika weight[i] > w
}

📝 Contoh Penggunaan

import { knapsack } from './knapsack';

const capacity = 8;
const weights = [3, 4, 5];
const values = [30, 50, 60];

const result = knapsack(capacity, weights, values);
console.log(result); // Output: 90

2. Coin Change

File: coin_change.ts

📖 Penjelasan

Diberikan jumlah uang dan daftar nilai koin yang tersedia (dengan jumlah tak terbatas), cari jumlah koin minimum yang diperlukan untuk membuat jumlah uang tersebut!

🎯 Contoh Kasus

Target: 13 rupiah
Koin tersedia: [7, 2, 3, 6]

Solusi:
• Cara 1: 6 + 7 = 2 koin ✓ (OPTIMAL)
• Cara 2: 2 + 2 + 2 + 2 + 2 + 3 = 6 koin
• Cara 3: 3 + 3 + 7 = 3 koin

Jawaban: 2 koin (6 dan 7)

🧮 Visualisasi Tabel DP

Tabel DP[i] = Jumlah koin minimum untuk membuat nilai i

Koin: [1, 5], Target: 10

Nilai:   0   1   2   3   4   5   6   7   8   9   10
Koin:   [0] [1] [2] [3] [4] [1] [2] [3] [4] [5] [2]
         ↑   ↑   ↑   ↑   ↑   ↑   ↑   ↑   ↑   ↑   ↑
        0¢  1¢  1+1 1+1 1+1 5¢  5+1 5+1 5+1 5+1 5+5

Penjelasan:
• DP[5] = 1 (satu koin 5)
• DP[6] = DP[5] + 1 = 2 (koin 5 + koin 1)
• DP[10] = DP[5] + 1 = 2 (koin 5 + koin 5) ✓

💻 Implementasi

export interface CoinChange {
  minCoins: number;  // Jumlah koin minimum
  coins: number[];   // Daftar koin yang digunakan
}

export const coinChange = (money: number, coins: number[]): CoinChange => {
  // Array untuk menyimpan jumlah koin minimum
  const minCoins: number[] = Array(money + 1).fill(Infinity);
  
  // Array untuk melacak koin terakhir yang digunakan
  const lastCoin: number[] = Array(money + 1).fill(-1);

  minCoins[0] = 0;  // Untuk nilai 0, butuh 0 koin

  // Untuk setiap koin
  for (const coin of coins) {
    // Coba buat semua nilai dari coin sampai target
    for (let j = coin; j <= money; j++) {
      // Jika menggunakan koin ini lebih optimal
      if (minCoins[j] > 1 + minCoins[j - coin]) {
        minCoins[j] = 1 + minCoins[j - coin];
        lastCoin[j] = coin;  // Catat koin yang digunakan
      }
    }
  }

  // Rekonstruksi solusi
  const result: CoinChange = {
    minCoins: minCoins[money],
    coins: []
  };

  let total = money;
  while (total > 0) {
    result.coins.push(lastCoin[total]);
    total -= lastCoin[total];
  }

  return result;
};

🔑 Formula DP

DP[amount] = min(DP[amount - coin] + 1) untuk semua coin dalam coins
             jika amount >= coin

Base case: DP[0] = 0 (tidak butuh koin untuk nilai 0)

📝 Contoh Penggunaan

import { coinChange } from './coin_change';

const result = coinChange(13, [7, 2, 3, 6]);
console.log(result);
// Output: { minCoins: 2, coins: [6, 7] }

3. Longest Common Subsequence (LCS)

File: lcs.ts

📖 Penjelasan

Diberikan dua string, cari subsequence terpanjang yang muncul di kedua string!

Subsequence = urutan karakter yang muncul dalam urutan yang sama (tidak harus berurutan).

🎯 Contoh Kasus

String 1: "ABCD"
String 2: "ACDF"

Subsequence yang sama:
• "A" ✓
• "AC" ✓
• "AD" ✓
• "ACD" ✓ (TERPANJANG!)
• "CD" ✓

Jawaban: "ACD" (panjang 3)

🧮 Visualisasi Tabel DP

Tabel DP[i][j] = Panjang LCS dari text1[0..i-1] dan text2[0..j-1]

text1 = "ABCD"
text2 = "ACDF"

        A   C   D   F
      ┌───┬───┬───┬───┐
    " │ 0 │ 0 │ 0 │ 0 │
      ├───┼───┼───┼───┤
  A   │ 1 │ 1 │ 1 │ 1 │  ← A == A, DP = 1
      ├───┼───┼───┼───┤
  B   │ 1 │ 1 │ 1 │ 1 │  ← B != C, ambil max(atas, kiri)
      ├───┼───┼───┼───┤
  C   │ 1 │ 2 │ 2 │ 2 │  ← C == C, DP = 1 + diagonal
      ├───┼───┼───┼───┤
  D   │ 1 │ 2 │ 3 │ 3 │  ← D == D, DP = 1 + diagonal
      └───┴───┴───┴───┘
                          Jawaban: 3 ("ACD")

Cara membaca: Dari pojok kanan bawah, mundur:
  - D: cocok ✓
  - C: cocok ✓  
  - A: cocok ✓
  Hasil: "ACD"

💻 Implementasi

export const longestCommonSubsequence = (
  text1: string,
  text2: string
): string => {
  const m = text1.length;
  const n = text2.length;

  // Buat tabel DP
  const dp: number[][] = Array.from({ length: m + 1 }, () =>
    Array(n + 1).fill(0)
  );

  // Isi tabel
  for (let i = 1; i <= m; i++) {
    for (let j = 1; j <= n; j++) {
      if (text1[i - 1] === text2[j - 1]) {
        // Karakter cocok, tambah 1 dari diagonal
        dp[i][j] = dp[i - 1][j - 1] + 1;
      } else {
        // Karakter beda, ambil nilai maksimal dari atas atau kiri
        dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
      }
    }
  }

  // Rekonstruksi LCS dari tabel
  let i = m, j = n;
  const lcs: string[] = [];
  
  while (i > 0 && j > 0) {
    if (text1[i - 1] === text2[j - 1]) {
      // Karakter cocok, masukkan ke hasil
      lcs.unshift(text1[i - 1]);
      i--;
      j--;
    } else if (dp[i - 1][j] > dp[i][j - 1]) {
      // Nilai atas lebih besar, naik
      i--;
    } else {
      // Nilai kiri lebih besar, ke kiri
      j--;
    }
  }

  return lcs.join('');
};

🔑 Formula DP

DP[i][j] = {
    0                                           jika i = 0 atau j = 0
    DP[i-1][j-1] + 1                            jika text1[i-1] == text2[j-1]
    max(DP[i-1][j], DP[i][j-1])                 jika text1[i-1] != text2[j-1]
}

📝 Contoh Penggunaan

import { longestCommonSubsequence } from './lcs';

console.log(longestCommonSubsequence('ABCD', 'ACDF'));
// Output: "ACD"

console.log(longestCommonSubsequence('AGGTAB', 'GXTXAYB'));
// Output: "GTAB"

Resume Bahasa Bayi 👶

🎒 Knapsack

"Kamu punya tas kecil tapi mau bawa barang banyak-banyak. Pilih barang yang paling berharga tapi muat di tas!"

Analogi: Seperti packing koper sebelum liburan - pilih baju yang paling penting dan muat di koper.


🪙 Coin Change

"Kamu mau bayar 13 ribu. Punya koin 7, 2, 3, 6. Berapa koin paling sedikit yang bisa kamu pakai?"

Analogi: Seperti membayar dengan uang receh - kita ingin jumlah lembar/koinnya paling sedikit biar dompet nggak tebal.


📝 LCS

"Cari huruf-huruf yang sama urutannya di dua kata berbeda!"

Analogi: Seperti mencari persamaan antara "KUCING" dan "KELINCI" → "K", "I", "N" muncul berurutan di keduanya!


🚀 Cara Menjalankan Test

# Jalankan semua test di folder ini
npm test -- dynamic_programming

# Jalankan test spesifik
npm test -- knapsack
npm test -- coin_change
npm test -- lcs

📖 Referensi Belajar


🤝 Kontribusi

Jika ingin menambahkan algoritma DP lainnya:

  1. Buat file .ts baru di folder ini
  2. Tambahkan test di folder test/
  3. Update dokumentasi ini

Selamat belajar Dynamic Programming! 🎉