Mengenali DP: Submasalah yang Saling Tumpang Tindih
Identifikasi kapan rekursi brute-force menyelesaikan submasalah yang sama berulang kali, gambar pohon rekursi Fibonacci, dan lihat ledakan eksponensialnya.
Mengenali DP: Submasalah yang Saling Tumpang Tindih adalah pelajaran DSA Interview Prep gratis di CoddyKit. Ini adalah pelajaran 1 dari 4. Kamu bisa membaca pelajaran lengkapnya di bawah secara gratis — lalu praktikkan langsung di browser dengan editor kode bawaan dan tutor AI 24/7. Ini adalah bagian dari jalur belajar DSA Interview Prep, dan progresmu tersinkronisasi di web dan aplikasi CoddyKit. Kursus DSA Interview Prep mencakup 4 pelajaran total.
Apa Itu Pemrograman Dinamis
Pemrograman dinamis (DP) menyelesaikan masalah kompleks dengan memecahnya menjadi submasalah yang lebih sederhana dan saling tumpang tindih, menyelesaikan setiap submasalah sekali, lalu menyimpan hasilnya untuk menghindari perhitungan berulang. DP berlaku ketika suatu masalah memiliki dua syarat: submasalah yang tumpang tindih (submasalah yang sama diselesaikan berkali-kali dalam rekursi naif) dan substruktur optimal (solusi optimal dapat dibangun dari solusi optimal submasalah). Tanpa kedua syarat tersebut, DP tidak membantu.
# Two ingredients of DP:
# 1. Overlapping sub-problems:
# fib(5) -> fib(4) + fib(3)
# fib(4) -> fib(3) + fib(2) <- fib(3) computed twice!
# Without caching: O(2^n) calls for Fibonacci
# 2. Optimal substructure:
# Shortest path from A to C through B:
# shortest(A,C) = shortest(A,B) + shortest(B,C)
# The sub-path A->B must itself be the shortest
# Contrast with greedy: greedy makes one locally optimal
# choice; DP tries all choices and picks the best.
print('DP = overlapping sub-problems + optimal substructure')Fibonacci: Titik Masuk Klasik ke DP
Urutan Fibonacci (fib(n) = fib(n-1) + fib(n-2)) adalah contoh klasik submasalah yang tumpang tindih. Rekursi naif memiliki waktu eksponensial O(2^n) karena menghitung ulang nilai yang sama berulang kali. Pohon rekursi untuk fib(6) menunjukkan bahwa fib(3) dihitung 3 kali, fib(2) 5 kali, dan seterusnya. Ledakan eksponensial ini tepatnya yang dihilangkan DP dengan menyimpan hasil yang telah dihitung.
import time
def fib_naive(n):
if n <= 1:
return n
return fib_naive(n-1) + fib_naive(n-2)
# Count the calls:
call_count = [0]
def fib_count(n):
call_count[0] += 1
if n <= 1: return n
return fib_count(n-1) + fib_count(n-2)
fib_count(10)
print(f'Calls for fib(10): {call_count[0]}') # 177 calls for n=10!
call_count[0] = 0
fib_count(20)
print(f'Calls for fib(20): {call_count[0]}') # 21891 calls
# n=30 -> ~2.7 million calls: exponential growthMemvisualisasikan Pohon Rekursi
Menggambar pohon rekursi untuk fib(5) mengungkap pemborosan: setiap simpul menghasilkan dua anak, dan subpohon identik muncul berulang kali. Jumlah total simpul dalam pohon tersebut adalah O(2^n). Saat Anda melihat pola ini — pemanggilan fungsi identik dengan argumen yang sama yang berulang dalam pohon — itu menandakan bahwa DP dapat membantu dengan menyimpan hasil. Keterampilan visualisasi ini sangat penting: jika Anda dapat mengenali subpohon yang berulang, Anda tahu bahwa DP dapat diterapkan.
# fib(5) recursion tree (simplified):
# fib(5)
# / \
# fib(4) fib(3)
# / \ / \
# fib(3) fib(2) fib(2) fib(1)
# / \ \
# fib(2) fib(1) fib(1)
# / \
# fib(1) fib(0)
# fib(3) appears TWICE
# fib(2) appears THREE TIMES
# Each redundant call wastes exponential time
# Key insight: fib(n) only has O(n) DISTINCT sub-problems
# (fib(0), fib(1), ..., fib(n))
# DP computes each ONCE -> O(n) total
print('Distinct sub-problems: O(n) but naive calls: O(2^n)')Mengidentifikasi Submasalah yang Tumpang Tindih
Untuk mengenali submasalah yang tumpang tindih: tulis rekursi pencarian menyeluruh, lalu tanyakan, 'apakah ada beberapa pemanggilan rekursif dengan argumen SAME?' Jika ya, DP dapat membantu. Sinyal umum dalam deskripsi masalah antara lain: 'jumlah minimum/maksimum X', 'berapa banyak cara untuk melakukan Y', dan 'bisakah kita mencapai Z?'. Pola ungkapan ini hampir selalu menunjukkan masalah dengan substruktur optimal, yang jawabannya pada posisi i bergantung pada jawaban di posisi-posisi sebelumnya.
# DP signal phrases in problem statements:
# 'minimum number of coins to make amount X'
# 'maximum profit from stock trades'
# 'number of ways to climb n stairs'
# 'can you reach the last index?'
# 'longest common subsequence'
# 'edit distance between two strings'
# All have this shape:
# solve(input) = f(solve(smaller_input_1), solve(smaller_input_2), ...)
# And multiple branches end up calling solve with the same argument.
# If the recursion tree has repeated nodes: DP
# If subproblems are all independent: divide-and-conquer (no DP needed)
print('Repeated arguments in recursion tree -> DP')Penjelasan tentang Substruktur Optimal
Substruktur optimal berarti solusi optimal suatu masalah dapat dibangun dari solusi optimal submasalahnya. Sebagai contoh, jalur terpendek dari A ke C melalui B bersifat optimal jika dan hanya jika subjalur A→B dan B→C masing-masing optimal. Jika sifat ini berlaku, Anda dapat membangun optimum global dari bawah ke atas berdasarkan optimum lokal. Masalah yang tidak memiliki substruktur optimal (misalnya, jalur terpanjang dalam graf umum yang memiliki siklus) tidak dapat diselesaikan dengan DP.
# Optimal substructure examples:
# SHORTEST PATH: shortest(A,C) = min over all B: shortest(A,B) + w(B,C)
# -> Sub-paths must be optimal: YES, has optimal substructure
# LONGEST PATH (no cycles, DAG): can also use DP
# -> Longer path through node B means sub-path A->B must be longest
# LONGEST PATH (with cycles): NO optimal substructure
# -> Best path from A to C might reuse nodes: sub-problems not independent
# COIN CHANGE: min coins for amount n = 1 + min(min coins for n-coin_i)
# -> YES: optimal for n-coin_i is needed for optimal n
print('Optimal substructure: build global optimum from local optima')Menaiki Tangga: DP Pertama Anda
Menaiki Tangga (LeetCode #70): berapa banyak cara berbeda untuk menaiki n anak tangga dengan mengambil 1 atau 2 langkah sekaligus? Misalkan dp[i] = jumlah cara untuk mencapai anak tangga i. Anda dapat tiba di anak tangga i dari anak tangga i-1 (satu langkah) atau i-2 (dua langkah), sehingga dp[i] = dp[i-1] + dp[i-2]. Ini adalah Fibonacci! Kasus dasar: dp[1] = 1, dp[2] = 2. Mengenali bahwa 'menaiki tangga' dapat direduksi menjadi Fibonacci merupakan wawasan klasik dalam wawancara.
def climb_stairs(n):
if n <= 2:
return n
dp = [0] * (n + 1)
dp[1] = 1 # 1 way to reach step 1
dp[2] = 2 # 2 ways to reach step 2: (1+1) or (2)
for i in range(3, n + 1):
dp[i] = dp[i-1] + dp[i-2] # come from i-1 or i-2
return dp[n]
for n in range(1, 8):
print(f'climb_stairs({n}) = {climb_stairs(n)}')
# 1, 2, 3, 5, 8, 13, 21 -- Fibonacci sequence!Kerangka DP: Keadaan, Rekurensi, Urutan Pengisian
Kerangka kerja DP 3 langkah yang andal: 1. Tentukan keadaan — apa yang direpresentasikan oleh dp[i] (atau dp[i][j])? Tuliskan dalam bahasa Inggris. 2. Tuliskan rekurensi — nyatakan dp[i] berdasarkan submasalah yang lebih kecil. Sertakan semua kasus. 3. Tentukan urutan pengisian — pastikan dp[i-1] (dan dependensi lainnya) dihitung sebelum dp[i]. Kasus dasar menginisialisasi batas. Kerangka ini mengubah intuisi DP yang samar menjadi rencana implementasi konkret.
# Framework applied to climbing stairs:
# Step 1 - Define state:
# dp[i] = number of distinct ways to reach step i
# Step 2 - Recurrence:
# dp[i] = dp[i-1] + dp[i-2] (come from step i-1 or i-2)
# Step 3 - Fill order:
# Compute dp[1], dp[2], dp[3], ..., dp[n] in order
# Because dp[i] depends on dp[i-1] and dp[i-2] (smaller)
# Base cases: dp[1]=1, dp[2]=2
# Framework applied to coin change:
# Step 1: dp[amount] = minimum coins to make that amount
# Step 2: dp[i] = 1 + min(dp[i-coin] for coin in coins if i >= coin)
# Step 3: Fill i from 1 to amount
# Base: dp[0] = 0 (zero coins for zero amount)
print('DP framework: define state -> recurrence -> fill order')Kapan NOT Menggunakan DP
DP tidak selalu menjadi jawaban. Gunakan algoritma rakus ketika satu pilihan yang optimal secara lokal selalu menghasilkan solusi yang optimal secara global (pemilihan aktivitas, permainan lompatan I). Gunakan pecah dan taklukkan ketika submasalah tidak saling tumpang tindih (pengurutan gabung, pencarian biner). Gunakan BFS ketika masalahnya adalah mencari jalur terpendek dalam graf tak berbobot. DP memang benar, tetapi sering kali berlebihan ketika tersedia pendekatan rakus atau pendekatan yang lebih sederhana. Dalam wawancara, diskusikan alasan Anda memilih DP dibandingkan alternatifnya.
# DP vs alternatives:
# Problem: can you jump to the end of the array?
# Greedy: track max reachable index -> O(n) O(1) BETTER than DP
# Problem: shortest path unweighted graph?
# BFS: O(V+E) BETTER than DP on general graph
# Problem: sort an array?
# Comparison sort: O(n log n), no DP needed
# DP IS the right choice when:
# - Greedy fails (choices interact)
# - Need to count/enumerate all possibilities
# - Problem has 'how many ways' or 'minimum/maximum' flavor
# - Recursion tree clearly shows overlapping sub-problems
print('Ask: does greedy fail? If yes, consider DP.')Menghitung Submasalah yang Berbeda
Jumlah submasalah yang berbeda menentukan kompleksitas waktu dan ruang DP. Untuk DP 1D pada masukan berukuran n, terdapat O(n) submasalah. Untuk DP 2D pada dua masukan berukuran m dan n, terdapat O(mn) submasalah. Setiap submasalah diselesaikan dalam waktu O(k) (untuk k pilihan pada setiap langkah), sehingga total waktunya O(n*k) atau O(mn*k). Selalu hitung submasalah yang berbeda terlebih dahulu — ini memberikan kompleksitas waktu DP bahkan sebelum Anda menulis kodenya.
# Sub-problem count examples:
# Problem | Sub-problems | Each costs | Total
# Fibonacci | O(n) | O(1) | O(n)
# Coin change | O(amount) | O(coins) | O(amount * coins)
# LCS (m,n chars) | O(m*n) | O(1) | O(m*n)
# Edit distance | O(m*n) | O(1) | O(m*n)
# 0/1 Knapsack | O(n*W) | O(1) | O(n*W)
# Matrix chain | O(n^2) | O(n) | O(n^3)
# Rule: DP time = (# distinct sub-problems) * (time per sub-problem)
print('Time = subproblems * work-per-subproblem')Perampokan Rumah: Pilihan yang Saling Tumpang Tindih
Perampokan Rumah (LeetCode #198) meminta jumlah maksimum yang dapat Anda rampok dari rumah-rumah yang berderet tanpa merampok rumah yang bersebelahan. Pada setiap rumah, Anda memilih: merampoknya (menambahkan nilainya dan melewati rumah sebelumnya) atau melewatinya (mengambil hasil terbaik dari rumah sebelumnya). dp[i] = max(dp[i-1], dp[i-2] + nums[i]). Pola pilihan pada setiap langkah ini adalah rekurensi DP 1D yang paling sederhana dan muncul dalam lusinan masalah wawancara.
def rob(nums):
if not nums: return 0
if len(nums) == 1: return nums[0]
dp = [0] * len(nums)
dp[0] = nums[0]
dp[1] = max(nums[0], nums[1])
for i in range(2, len(nums)):
dp[i] = max(dp[i-1], # skip house i
dp[i-2] + nums[i]) # rob house i
return dp[-1]
print(rob([1, 2, 3, 1])) # 4: rob house 0 and 2 (1+3)
print(rob([2, 7, 9, 3, 1]))# 12: rob house 0, 2, 4 (2+9+1)
print(rob([2, 1, 1, 2])) # 4: rob house 0 and 3Pemeriksaan Kewajaran: Pencarian Menyeluruh vs DP
Selalu verifikasi DP Anda dengan solusi pencarian menyeluruh pada masukan kecil. Solusi pencarian menyeluruh menjadi acuan kebenaran Anda. Setelah DP cocok dengan solusi pencarian menyeluruh pada semua kasus uji, Anda dapat mengetahui bahwa rekurensinya benar. Hanya setelah itu lakukan optimisasi ruang. Pendekatan berbasis pengujian ini — pencarian menyeluruh → DP dari atas ke bawah → DP dari bawah ke atas → DP yang dioptimalkan ruang — merupakan cara profesional untuk mengembangkan dan memverifikasi solusi DP selama wawancara.
# Brute-force for house robber (exponential)
def rob_brute(nums, i=0):
if i >= len(nums):
return 0
# Option 1: rob house i
rob_it = nums[i] + rob_brute(nums, i + 2)
# Option 2: skip house i
skip_it = rob_brute(nums, i + 1)
return max(rob_it, skip_it)
# Verify on small inputs:
test_cases = [[1,2,3,1], [2,7,9,3,1], [2,1,1,2]]
for tc in test_cases:
bf = rob_brute(tc)
dp = rob(tc)
print(f'{tc}: brute={bf}, dp={dp}, match={bf==dp}')Pemeriksaan Cepat
Uji pemahaman Anda tentang konsep Struktur Data & Algoritma — Persiapan Wawancara Pemrograman dari pelajaran ini.
Ringkasan Pelajaran
Dalam pelajaran ini Anda mempelajari: dua unsur DP (submasalah yang saling tumpang tindih dan struktur optimal), cara memvisualisasikan pohon rekursi untuk mengidentifikasi pemanggilan berulang, kerangka kerja DP tiga langkah (menentukan keadaan, rekurensi, dan urutan pengisian), serta contoh pertama yang mencakup Fibonacci, menaiki tangga, dan perampokan rumah. Berikutnya kita mengimplementasikan DP dari atas ke bawah dengan memoization.
Pertanyaan yang Sering Diajukan
Apakah pelajaran “Mengenali DP: Submasalah yang Saling Tumpang Tindih” gratis?
Ya — teks lengkap “Mengenali DP: Submasalah yang Saling Tumpang Tindih” gratis dibaca di sini di web. Untuk praktiknya secara interaktif (editor kode bawaan dan tutor AI 24/7) dan buka sisa kursus DSA Interview Prep, upgrade ke CoddyKit PRO. Kursus DSA Interview Prep mencakup 4 pelajaran total.
Apa yang akan aku pelajari di “Mengenali DP: Submasalah yang Saling Tumpang Tindih”?
Identifikasi kapan rekursi brute-force menyelesaikan submasalah yang sama berulang kali, gambar pohon rekursi Fibonacci, dan lihat ledakan eksponensialnya. Kamu berlatih DSA Interview Prep dengan kode praktik yang langsung kamu jalankan di browser, dan tutor AI 24/7 menjawab pertanyaanmu saat kamu mengerjakan pelajaran ini.
Apakah aku perlu pengalaman untuk memulai DSA Interview Prep?
Tidak diperlukan pengalaman sebelumnya. DSA Interview Prep di CoddyKit dirancang untuk pemula hingga pelajar tingkat lanjut, jadi kamu bisa memulai di sini atau dari awal dan belajar sesuai kecepatan kamu sendiri. Ini adalah pelajaran 1 dari 4.
Berapa lama pelajaran “Mengenali DP: Submasalah yang Saling Tumpang Tindih” memakan waktu?
Sebagian besar pelajaran CoddyKit memakan waktu sekitar 5–10 menit. Setiap pelajaran ringkas dan interaktif, jadi kamu membuat kemajuan stabil dan melanjutkan dari tempat kamu tinggalkan di web dan aplikasi.
Bisakah aku menulis dan menjalankan kode dalam pelajaran DSA Interview Prep ini?
Ya. Setiap pelajaran DSA Interview Prep menyertakan editor kode bawaan, jadi kamu menulis dan menjalankan kode nyata langsung di browser dan mendapatkan umpan balik AI instan — tidak diperlukan penyiapan lokal.
Semua pelajaran dalam kursus ini
- Mengenali DP: Submasalah yang Saling Tumpang Tindih
- DP Top-Down dengan Memoization
- DP Bottom-Up dengan Tabulasi
- Coin Change dan Tangga Berbiaya Minimum