Catatan: Folder ini berisi implementasi algoritma-algoritma Dynamic Programming yang populer dan sering digunakan.
- Apa itu Dynamic Programming?
- Memoization vs Tabulation
- Karakteristik Masalah DP
- Algoritma yang Tersedia
- Resume Bahasa Bayi
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.
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 😊
┌─────────────────────────────────────────────────────────────┐
│ "Jangan menghitung sesuatu yang sudah pernah dihitung!" │
└─────────────────────────────────────────────────────────────┘
DP memiliki dua pendekatan utama:
- 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! ✓]
- 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
| 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 |
Bagaimana mengetahui suatu masalah bisa diselesaikan dengan DP?
-
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
-
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!
Tanyakan pada dirimu:
- "Apakah saya bisa memecah ini menjadi bagian lebih kecil?"
- "Apakah ada perhitungan yang berulang?"
- "Apakah solusi optimal bergantung pada solusi optimal bagian kecil?"
Jika jawabannya YA untuk semua → Gunakan DP! 🎉
File: knapsack.ts
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.
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)
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
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];
};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
}
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: 90File: coin_change.ts
Diberikan jumlah uang dan daftar nilai koin yang tersedia (dengan jumlah tak terbatas), cari jumlah koin minimum yang diperlukan untuk membuat jumlah uang tersebut!
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)
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) ✓
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;
};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)
import { coinChange } from './coin_change';
const result = coinChange(13, [7, 2, 3, 6]);
console.log(result);
// Output: { minCoins: 2, coins: [6, 7] }File: lcs.ts
Diberikan dua string, cari subsequence terpanjang yang muncul di kedua string!
Subsequence = urutan karakter yang muncul dalam urutan yang sama (tidak harus berurutan).
String 1: "ABCD"
String 2: "ACDF"
Subsequence yang sama:
• "A" ✓
• "AC" ✓
• "AD" ✓
• "ACD" ✓ (TERPANJANG!)
• "CD" ✓
Jawaban: "ACD" (panjang 3)
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"
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('');
};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]
}
import { longestCommonSubsequence } from './lcs';
console.log(longestCommonSubsequence('ABCD', 'ACDF'));
// Output: "ACD"
console.log(longestCommonSubsequence('AGGTAB', 'GXTXAYB'));
// Output: "GTAB""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.
"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.
"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!
# Jalankan semua test di folder ini
npm test -- dynamic_programming
# Jalankan test spesifik
npm test -- knapsack
npm test -- coin_change
npm test -- lcsJika ingin menambahkan algoritma DP lainnya:
- Buat file
.tsbaru di folder ini - Tambahkan test di folder
test/ - Update dokumentasi ini
Selamat belajar Dynamic Programming! 🎉