Templat Backtracking: Pilih, Jelajahi, Batalkan Pilihan
Terapkan kerangka penelusuran mundur tiga langkah, telusuri kerjanya pada contoh kecil, dan identifikasi di bagian mana kondisi pemangkasan ditempatkan
Templat Backtracking: Pilih, Jelajahi, Batalkan Pilihan adalah pelajaran Coding 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 Coding Interview Prep, dan progresmu tersinkronisasi di web dan aplikasi CoddyKit. Kursus Coding Interview Prep mencakup 4 pelajaran total.
Apa Itu Penelusuran Mundur?
Penelusuran mundur adalah metode sistematis untuk menemukan semua (atau sebagian) hasil dengan menjelajahi setiap kandidat secara bertahap dan meninggalkan (memangkas) sebuah cabang segera setelah ditentukan bahwa cabang tersebut tidak mungkin menghasilkan hasil yang valid. Metode ini digunakan untuk menyelesaikan Sudoku, menghasilkan permutasi, dan menemukan semua kombinasi yang valid. Bayangkan metode ini sebagai pencarian mendalam-terlebih-dahulu pada pohon keputusan.
# Mental model: backtracking explores a decision tree
# At each node you make a choice, go deeper, then undo it
#
# Tree for generating subsets of [1,2,3]:
# []
# / \
# [1] []
# / \ / \
# [1,2][1][2] []
# ...
# Every leaf is a potential solution
# Pruning cuts branches early based on constraints
print('Backtracking = DFS on decision tree with pruning')Templat Tiga Langkah
Setiap fungsi penelusuran mundur mengikuti tiga langkah: Pilih—pilih kandidat berikutnya dari opsi yang tersedia. Jelajahi—lakukan rekursi dengan pilihan tersebut, bergerak satu tingkat lebih dalam pada pohon keputusan. Batalkan Pilihan—batalkan pilihan setelah kembali dari rekursi untuk memulihkan keadaan sebelum mencoba kandidat berikutnya. Pola ini juga disebut tambah/rekursi/hapus atau tandai/rekursi/batalkan tanda dalam konteks yang berbeda.
def backtrack(current_state, choices, results):
# Base case: is current_state a complete solution?
if is_complete(current_state):
results.append(list(current_state)) # record solution
return
for choice in choices:
if is_valid(choice, current_state): # pruning condition
# 1. CHOOSE
current_state.append(choice)
# 2. EXPLORE
backtrack(current_state, choices, results)
# 3. UNCHOOSE (backtrack)
current_state.pop()
# Placeholder functions — filled per problem
def is_complete(state): return True
def is_valid(choice, state): return TrueContoh Paling Sederhana: Semua Himpunan Bagian
Hasilkan semua himpunan bagian dari [1, 2, 3]. Pada setiap indeks, kita memilih untuk menyertakan atau mengecualikan elemen tersebut. Indeks awal maju setelah setiap pemanggilan sehingga kita tidak mengunjungi kembali elemen sebelumnya. Tidak diperlukan pemeriksaan batasan—setiap keadaan parsial valid. Proses ini menghasilkan 2ⁿ himpunan bagian. Langkah pembatalan pilihan dilakukan dengan path.pop() setelah pemanggilan rekursif.
def subsets(nums):
result = []
def backtrack(start, path):
result.append(list(path)) # every state is a valid subset
for i in range(start, len(nums)):
path.append(nums[i]) # CHOOSE
backtrack(i + 1, path) # EXPLORE
path.pop() # UNCHOOSE
backtrack(0, [])
return result
print(subsets([1, 2, 3]))
# [[], [1], [1,2], [1,2,3], [1,3], [2], [2,3], [3]]Mengidentifikasi Kondisi Pemangkasan
Kekuatan penelusuran mundur dibandingkan pencarian menyeluruh terletak pada pemangkasan: mengenali sejak dini bahwa jalur parsial tidak dapat menghasilkan hasil yang valid. Untuk jumlah kombinasi (jumlah target dengan batas), setelah jumlah berjalan melebihi target, cabang yang lebih dalam hanya akan menghasilkan nilai yang lebih besar—pangkas dengan segera mengembalikan hasil. Untuk N-ratu, jika seorang ratu menyerang ratu yang sudah ada, lewati kolom tersebut. Pemangkasan mengubah pohon eksponensial menjadi pencarian yang dapat dikelola.
def combination_sum(candidates, target):
result = []
candidates.sort() # sort enables early termination
def backtrack(start, path, remaining):
if remaining == 0:
result.append(list(path))
return
for i in range(start, len(candidates)):
c = candidates[i]
if c > remaining: break # PRUNE: sorted, so rest are bigger too
path.append(c) # CHOOSE
backtrack(i, path, remaining - c) # EXPLORE (reuse allowed)
path.pop() # UNCHOOSE
backtrack(0, [], target)
return result
print(combination_sum([2, 3, 6, 7], 7)) # [[2,2,3],[7]]Pemulihan Keadaan Sangat Penting
Kesalahan umum dalam penelusuran mundur adalah tidak memulihkan keadaan sepenuhnya sebelum iterasi berikutnya. Jika Anda menggunakan struktur data yang dapat diubah (daftar, himpunan, kisi), setiap perubahan yang dibuat selama Pilih harus dibalik selama Batalkan Pilihan. Misalnya, saat mengubah kisi seperti pada Sudoku atau Pencarian Kata, kosongkan sel tersebut setelah pemanggilan rekursif. Jika langkah ini dilupakan, keadaan akan rusak untuk cabang-cabang sejajar.
# Bug: forgetting to unmark in word search
# Correct pattern for grid backtracking:
def word_search(board, word):
m, n = len(board), len(board[0])
def dfs(r, c, k):
if k == len(word): return True
if not (0<=r<m and 0<=c<n): return False
if board[r][c] != word[k]: return False
temp, board[r][c] = board[r][c], '#' # CHOOSE (mark visited)
found = any(dfs(r+dr, c+dc, k+1)
for dr, dc in [(0,1),(0,-1),(1,0),(-1,0)])
board[r][c] = temp # UNCHOOSE (restore cell)
return found
return any(dfs(r, c, 0) for r in range(m) for c in range(n))
board = [['A','B','C','E'],['S','F','C','S'],['A','D','E','E']]
print(word_search([row[:] for row in board], 'ABCCED')) # TrueMenelusuri Pohon Keputusan
Untuk jumlah kombinasi dengan [2, 3, 6, 7] dan target 7, telusuri pohonnya: di akar, coba 2. Dari 2, coba 2 lagi (remaining=3). Dari 2+2, coba 2 lagi (remaining=1). 2>1, jadi pangkas. Coba 3: 3>1, pangkas. Lakukan backtrack. Dari 2+2, coba 3 (remaining=3). 3 sama dengan nilai yang tersisa: catat [2,2,3]. Lakukan backtrack dan lanjutkan. Penelusuran ini menunjukkan bagaimana pemangkasan menghilangkan cabang sebelum menghasilkan hasil yang tidak valid.
def combination_sum_trace(candidates, target):
result = []
candidates.sort()
def backtrack(start, path, remaining, depth):
indent = ' ' * depth
print(f'{indent}explore({path}, remaining={remaining})')
if remaining == 0:
result.append(list(path))
print(f'{indent}FOUND: {path}')
return
for i in range(start, len(candidates)):
c = candidates[i]
if c > remaining:
print(f'{indent}PRUNE at {c}')
break
path.append(c)
backtrack(i, path, remaining - c, depth + 1)
path.pop()
backtrack(0, [], target, 0)
return result
combination_sum_trace([2, 3, 6, 7], 7)Penelusuran Mundur vs Pencarian Menyeluruh
Pencarian menyeluruh mencoba semua hasil lengkap yang mungkin lalu memvalidasi masing-masing. Penelusuran mundur melakukan pemangkasan selama konstruksi, sehingga tidak pernah menyelesaikan jalur yang tidak valid. Untuk N-ratu dengan N=8, pencarian menyeluruh memeriksa 8^8 = 16 juta penempatan. Penelusuran mundur menguranginya menjadi sekitar 2.057 pemanggilan rekursif. Perbedaannya meningkat drastis seiring bertambahnya N: untuk N=12, pencarian menyeluruh mencoba 8,9 miliar penempatan, sedangkan penelusuran mundur hanya menjelajahi sebagian kecil pohon.
# Compare call counts: brute force vs backtracking for permutations
import sys
calls_brute = [0]
calls_back = [0]
def brute_force_perms(nums):
from itertools import permutations
return list(permutations(nums))
def backtrack_perms(nums):
result = []
used = [False] * len(nums)
def bt(path):
calls_back[0] += 1
if len(path) == len(nums):
result.append(list(path))
return
for i, n in enumerate(nums):
if not used[i]:
used[i] = True
path.append(n)
bt(path)
path.pop()
used[i] = False
bt([])
return result
backtrack_perms([1,2,3,4])
print(f'Backtrack calls for 4 items: {calls_back[0]}')Mengumpulkan vs Mengembalikan Lebih Awal
Masalah penelusuran mundur terbagi menjadi dua kategori: menghasilkan semua hasil (mengumpulkan setiap jalur lengkap) atau menemukan satu hasil saja (mengembalikan True segera setelah sebuah jalur berhasil). Untuk enumerasi, selalu gunakan append untuk menambahkan hasil ke dalam daftar. Untuk pencarian satu hasil, segera kembalikan True dari pemanggilan rekursif dan teruskan nilai tersebut ke tingkat atas. Mengembalikan any(backtrack(...)) atau menggunakan if backtrack(...): return True menerapkan perilaku berhenti segera.
# Enumerate all: collect in results list
def all_solutions(candidates):
results = []
def bt(path, remaining):
if remaining == 0:
results.append(list(path))
return
for c in candidates:
if c <= remaining:
path.append(c); bt(path, remaining - c); path.pop()
bt([], 5)
return results
# Find any one: return True on first success
def any_solution(candidates, target):
def bt(path, remaining):
if remaining == 0: return True
for c in candidates:
if c <= remaining:
path.append(c)
if bt(path, remaining - c): return True # short-circuit
path.pop()
return False
path = []
return bt(path, target), pathMemoisasi dengan Penelusuran Mundur
Penelusuran mundur murni menjelajahi setiap jalur tanpa penyimpanan tembolok, dan ini baik-baik saja saat semua hasil diperlukan. Namun, beberapa masalah penelusuran mundur memiliki submasalah yang saling tumpang tindih. Misalnya, Pemenggalan Kata II dapat diselesaikan dengan penelusuran mundur + memoisasi: simpan dalam tembolok daftar kalimat yang mungkin dibuat dari setiap indeks awal. Ini mengubah penelusuran mundur dengan waktu terburuk eksponensial menjadi algoritma dengan waktu polinomial. Kenali pengulangan submasalah agar dapat menerapkan pendekatan gabungan ini.
from functools import lru_cache
def word_break_all(s, wordDict):
words = set(wordDict)
@lru_cache(maxsize=None)
def bt(start):
if start == len(s): return [''] # empty suffix
result = []
for end in range(start + 1, len(s) + 1):
word = s[start:end]
if word in words:
for rest in bt(end):
result.append(word if not rest else word + ' ' + rest)
return result
return bt(0)
print(word_break_all('catsanddog', ['cat','cats','and','sand','dog']))
# ['cat sand dog', 'cats and dog']Kompleksitas Waktu Penelusuran Mundur
Kompleksitas waktu penelusuran mundur bergantung pada jumlah daun dalam pohon keputusan dikalikan dengan pekerjaan per simpul. Untuk himpunan bagian: O(n × 2ⁿ). Untuk permutasi: O(n × n!). Untuk jumlah kombinasi: O(target/min_candidate ^ n) dalam kasus terburuk. Pemangkasan mengurangi konstanta, tetapi tidak mengubah batas asimtotik. Saat ditanya tentang kompleksitas dalam wawancara, berikan ukuran pohon kasus terburuk dan sebutkan bahwa pemangkasan biasanya membuat algoritma jauh lebih cepat dalam praktik.
# Complexity quick reference:
# Subsets of n elements: O(n * 2^n) - 2^n subsets, each copied in O(n)
# Permutations of n: O(n * n!) - n! perms, each copied in O(n)
# Combination sum (target T): O(T^n / n!) worst case without pruning
# N-Queens: O(n!) - prune reduces practical count
# For n=10 permutations: 10! = 3,628,800 paths
import math
n = 10
print(f'n={n}: n!={math.factorial(n):,} paths')
print(f'n={n}: 2^n={2**n:,} subsets')Mengidentifikasi Masalah Penelusuran Mundur
Tanda-tanda bahwa sebuah masalah memerlukan penelusuran mundur: (1) temukan semua atau hasilkan semua kombinasi, permutasi, atau himpunan bagian. (2) Masalah melibatkan penempatan elemen atau orang berdasarkan batasan (N-ratu, Sudoku). (3) Ruang hasil bersifat eksponensial, tetapi batasan menghilangkan sebagian besar cabang sejak awal. (4) Anda perlu menjelajahi jalur dalam graf atau kisi yang mungkin mengunjungi kembali keadaan. Saat melihat tanda-tanda ini, gunakan templat pilih-jelajahi-batalkan pilihan.
# Common backtracking problem types:
# 1. Subsets / Power set
# 2. Permutations (with/without duplicates)
# 3. Combinations (k from n, combination sum)
# 4. Grid path finding (word search, unique paths with visited tracking)
# 5. Constraint satisfaction (N-queens, Sudoku solver)
# 6. String partitioning (palindrome partition, word break all)
# Template reminder:
def backtrack(start, path):
# base case: add to results or return True
for choice in get_choices(start):
if is_valid(choice, path): # prune
path.append(choice) # choose
backtrack(start+1, path) # explore
path.pop() # unchoose
def get_choices(start): return []
def is_valid(c, p): return TruePemeriksaan Singkat
Uji pemahaman Anda tentang konsep Struktur Data & Algoritma — Persiapan Wawancara Pemrograman dari pelajaran ini.
Rangkuman Pelajaran
Dalam pelajaran ini Anda telah mempelajari: templat penelusuran mundur memiliki tiga langkah—pilih, jelajahi, batalkan pilihan—yang masing-masing berarti menambahkan pilihan, melakukan rekursi, dan menghapusnya, kondisi pemangkasan menghilangkan cabang sejak awal dan menjadikan penelusuran mundur praktis dibandingkan pencarian menyeluruh, serta keadaan harus dipulihkan sepenuhnya setelah setiap pemanggilan rekursif agar cabang-cabang sejajar tidak rusak. Selanjutnya kita akan menerapkan templat ini untuk menghasilkan semua Himpunan Bagian dan Himpunan Kuasa.
Pertanyaan yang Sering Diajukan
Apakah pelajaran “Templat Backtracking: Pilih, Jelajahi, Batalkan Pilihan” gratis?
Ya — teks lengkap “Templat Backtracking: Pilih, Jelajahi, Batalkan Pilihan” gratis dibaca di sini di web. Untuk praktiknya secara interaktif (editor kode bawaan dan tutor AI 24/7) dan buka sisa kursus Coding Interview Prep, upgrade ke CoddyKit PRO. Kursus Coding Interview Prep mencakup 4 pelajaran total.
Apa yang akan aku pelajari di “Templat Backtracking: Pilih, Jelajahi, Batalkan Pilihan”?
Terapkan kerangka penelusuran mundur tiga langkah, telusuri kerjanya pada contoh kecil, dan identifikasi di bagian mana kondisi pemangkasan ditempatkan Kamu berlatih Coding 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 Coding Interview Prep?
Tidak diperlukan pengalaman sebelumnya. Coding 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 “Templat Backtracking: Pilih, Jelajahi, Batalkan Pilihan” 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 Coding Interview Prep ini?
Ya. Setiap pelajaran Coding 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
- Templat Backtracking: Pilih, Jelajahi, Batalkan Pilihan
- Himpunan Bagian dan Himpunan Kuasa
- Permutasi dan Kombinasi
- N-Queens dan Propagasi Kendala