Word Break dan Segmentasi String
Gunakan tabel DP 1D untuk menentukan apakah string dapat dipecah menjadi kata-kata kamus, analisis waktu O(n²), dan pahami alasan trie dapat mempercepatnya.
Word Break dan Segmentasi String adalah pelajaran DSA Interview Prep gratis di CoddyKit. Ini adalah pelajaran 3 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.
Masalah Pemisahan Kata
Pemisahan Kata (LeetCode 139) menanyakan: dengan diberikan untaian karakter s dan kamus kata, tentukan apakah s dapat dipecah menjadi urutan satu atau beberapa kata dari kamus yang dipisahkan oleh spasi. Sebagai contoh, dengan s = 'leetcode' dan wordDict = ['leet', 'code'], jawabannya adalah True karena 'leet' + 'code' = 'leetcode'. Ini adalah masalah DP 1D klasik.
s = 'leetcode'
word_set = {'leet', 'code'}
# Can we split 'leetcode' into words from word_set?
# 'leet' in set → yes, 'code' in set → yes
# So: 'leetcode' = 'leet' + 'code' → True
s2 = 'catsandog'
word_set2 = {'cats', 'dog', 'sand', 'and', 'cat'}
# No matter how we split, last part 'og' not in dict
print('Expected: True, False')Perumusan dan Keadaan DP
Definisikan dp[i] sebagai True jika subuntaian s[:i] dapat dipecah menggunakan kamus. Kasus dasarnya adalah dp[0] = True (untaian karakter kosong selalu dapat dipecah). Untuk setiap posisi i, periksa semua posisi j < i: jika dp[j] bernilai benar dan s[j:i] terdapat dalam kamus, maka dp[i] = True. Jawaban akhirnya adalah dp[len(s)].
def word_break(s, word_dict):
word_set = set(word_dict)
n = len(s)
dp = [False] * (n + 1)
dp[0] = True # empty string
for i in range(1, n + 1):
for j in range(i):
# If s[:j] is segmentable AND s[j:i] is a word
if dp[j] and s[j:i] in word_set:
dp[i] = True
break # no need to check other j values
return dp[n]
print(word_break('leetcode', ['leet', 'code'])) # True
print(word_break('catsandog', ['cats','dog','sand','and','cat'])) # FalseMenelusuri Tabel DP
Untuk s = 'leetcode' dan kamus {'leet', 'code'}: dp[0]=T. Pada i=4: j=0, dp[0]=T dan s[0:4]='leet' terdapat dalam kamus → dp[4]=T. Pada i=8: j=4, dp[4]=T dan s[4:8]='code' terdapat dalam kamus → dp[8]=T. Semua posisi lain yang tidak diakhiri kata tetap bernilai salah. Jawaban dp[8]=True mengonfirmasi bahwa untaian karakter tersebut dapat dipecah.
def word_break_trace(s, word_dict):
word_set = set(word_dict)
n = len(s)
dp = [False] * (n + 1)
dp[0] = True
for i in range(1, n + 1):
for j in range(i):
if dp[j] and s[j:i] in word_set:
dp[i] = True
print(f'dp[{i}]=True via s[{j}:{i}]={repr(s[j:i])}')
break
print('dp table:', dp)
return dp[n]
word_break_trace('leetcode', ['leet', 'code'])Analisis Kompleksitas Waktu
DP naif berjalan dalam waktu O(n²): n iterasi luar dikalikan hingga n iterasi dalam. Namun, pengambilan potongan s[j:i] juga memerlukan biaya O(n), sehingga kompleksitas sebenarnya dalam Python menjadi O(n³). Salah satu pengoptimalan adalah menelusuri kata-kata dalam kamus dan memeriksa apakah setiap kata berakhir pada posisi i, sehingga menghasilkan O(n × W × L), dengan W sebagai ukuran kamus dan L sebagai panjang kata rata-rata. Untuk sebagian besar masukan wawancara, O(n²) atau O(n³) dapat diterima.
# Slightly faster: iterate over words rather than all j positions
def word_break_v2(s, word_dict):
word_set = set(word_dict)
n = len(s)
dp = [False] * (n + 1)
dp[0] = True
for i in range(1, n + 1):
for word in word_set:
wl = len(word)
# Does 'word' end exactly at position i?
if i >= wl and dp[i - wl] and s[i - wl:i] == word:
dp[i] = True
break
return dp[n]
print(word_break_v2('applepenapple', ['apple', 'pen'])) # TrueAlternatif Rekursi dengan Memorasi
Masalah yang sama dapat diselesaikan secara top-down dengan memorasi. Definisikan fungsi rekursif can_break(start) yang mengembalikan nilai benar jika s[start:] dapat dipecah. Coba setiap kata sebagai prefiks s[start:], lalu lakukan rekursi pada sisanya. Simpan hasil untuk menghindari penelusuran ulang indeks awal yang sama berkali-kali. Pendekatan ini setara dengan DP bottom-up, tetapi dalam praktiknya dapat lebih cepat jika banyak posisi dipangkas lebih awal.
from functools import lru_cache
def word_break_memo(s, word_dict):
word_set = set(word_dict)
@lru_cache(maxsize=None)
def can_break(start):
if start == len(s): return True
for end in range(start + 1, len(s) + 1):
if s[start:end] in word_set and can_break(end):
return True
return False
return can_break(0)
print(word_break_memo('leetcode', ['leet', 'code'])) # True
print(word_break_memo('catsandog', ['cats','dog','sand','and','cat'])) # FalseMengembalikan Semua Pemisahan yang Valid
Pemisahan Kata II (LeetCode 140) meminta semua kemungkinan pemisahan. Pendekatannya adalah penelusuran mundur dengan memorasi: lakukan rekursi dari setiap posisi dan, ketika sebuah kata cocok, lakukan rekursi pada sisanya. Simpan semua hasil sementara sebagai daftar untaian karakter. Untuk menghindari TLE, simpan dalam memorasi daftar kalimat yang mungkin dibentuk dari setiap indeks awal. Jumlah kalimat dapat bersifat eksponensial dalam kasus terburuk, tetapi memorasi menghilangkan perhitungan yang berulang.
from functools import lru_cache
def word_break_ii(s, word_dict):
word_set = set(word_dict)
@lru_cache(maxsize=None)
def break_from(start):
if start == len(s): return ['']
results = []
for end in range(start + 1, len(s) + 1):
word = s[start:end]
if word in word_set:
for rest in break_from(end):
results.append(word if not rest else word + ' ' + rest)
return results
return break_from(0)
print(word_break_ii('catsanddog', ['cat','cats','and','sand','dog']))
# ['cat sand dog', 'cats and dog']Pengoptimalan Pohon Awalan
Saat kamus berukuran besar atau kata-katanya panjang, pemeriksaan s[j:i] in word_set untuk semua j berjalan lambat karena pengacakan untaian karakter Python. Pohon awalan memungkinkan Anda menelusuri pohon karakter demi karakter dan memangkas jalur yang mustahil lebih awal. Alih-alih memeriksa semua posisi awal O(n), Anda hanya mengikuti jalur yang ada di pohon. Hal ini secara signifikan mengurangi waktu eksekusi dalam praktik ketika hanya sedikit prefiks yang menghasilkan kata valid.
class TrieNode:
def __init__(self):
self.children = {}
self.is_end = False
def build_trie(words):
root = TrieNode()
for word in words:
node = root
for ch in word:
node = node.children.setdefault(ch, TrieNode())
node.is_end = True
return root
def word_break_trie(s, word_dict):
root = build_trie(word_dict)
n = len(s)
dp = [False] * (n + 1)
dp[0] = True
for i in range(n):
if not dp[i]: continue
node = root
for j in range(i, n):
ch = s[j]
if ch not in node.children: break
node = node.children[ch]
if node.is_end:
dp[j + 1] = True
return dp[n]
print(word_break_trie('leetcode', ['leet', 'code'])) # TrueKasus Tepi dan Batasan
Kasus tepi yang penting: (1) Untaian karakter kosong: kembalikan True (untaian karakter kosong secara langsung dapat dipecah). (2) Kata tidak ada dalam kamus: dp tidak pernah menetapkan posisi yang sesuai menjadi True, sehingga mengembalikan False dengan benar. (3) Kata yang tumpang tindih: misalnya, 'a' dan 'aa' dalam kamus dengan s='aaa' — DP menanganinya secara alami dengan memeriksa semua nilai j. (4) Karakter berulang: s='aaaaab' dengan dict=['a','aa','aaa'] — terdapat jalur eksponensial, tetapi memorasi membatasinya menjadi O(n²).
def word_break(s, word_dict):
word_set = set(word_dict)
dp = [False] * (len(s) + 1)
dp[0] = True
for i in range(1, len(s) + 1):
for j in range(i):
if dp[j] and s[j:i] in word_set:
dp[i] = True
break
return dp[len(s)]
# Edge cases
print(word_break('', ['hello'])) # True (empty string)
print(word_break('a', ['b'])) # False
print(word_break('aaa', ['a', 'aa'])) # True (many ways)Generalisasi Pemisahan Untaian Karakter
Pemisahan Kata dapat digeneralisasi ke masalah pemisahan untaian karakter apa pun: apakah untaian karakter s dapat dipartisi menurut aturan tertentu? Ganti pencarian kamus dengan pemeriksaan O(1) atau O(L) apa pun. Misalnya: apakah s dapat dipartisi menjadi palindrom? Gunakan tabel palindrom yang telah dihitung sebelumnya, bukan kumpulan kata. Struktur DP-nya sama — hanya pemeriksaan validitasnya yang berubah.
def palindrome_partition_possible(s):
'''Can s be partitioned into palindromes? (Always yes — single chars are palindromes)'''
n = len(s)
# Precompute palindrome table
is_pal = [[False]*n for _ in range(n)]
for i in range(n): is_pal[i][i] = True
for i in range(n-1): is_pal[i][i+1] = (s[i]==s[i+1])
for length in range(3, n+1):
for i in range(n-length+1):
j = i + length - 1
is_pal[i][j] = s[i]==s[j] and is_pal[i+1][j-1]
# DP similar to word break
dp = [False] * (n + 1)
dp[0] = True
for i in range(1, n + 1):
for j in range(i):
if dp[j] and is_pal[j][i-1]:
dp[i] = True
break
return dp[n]
print(palindrome_partition_possible('aab')) # True (a,a,b or aa,b)Pendekatan DP vs BFS
Pemisahan Kata juga dapat dirumuskan sebagai masalah jalur terpendek BFS: setiap posisi dalam untaian karakter adalah sebuah simpul, dan terdapat sisi dari j ke i jika s[j:i] terdapat dalam kamus. BFS dari simpul 0 menanyakan apakah simpul n dapat dicapai. BFS menghasilkan kompleksitas O(n² × L) yang sama, tetapi mungkin lebih intuitif jika Anda memodelkannya sebagai masalah graf saat wawancara.
from collections import deque
def word_break_bfs(s, word_dict):
word_set = set(word_dict)
n = len(s)
visited = set()
queue = deque([0])
while queue:
start = queue.popleft()
if start == n: return True
for end in range(start + 1, n + 1):
if end not in visited and s[start:end] in word_set:
visited.add(end)
queue.append(end)
return False
print(word_break_bfs('leetcode', ['leet', 'code'])) # True
print(word_break_bfs('catsandog', ['cats','dog','and','sand','cat'])) # FalseStrategi Komunikasi Wawancara
Dalam wawancara, jelaskan proses berpikir berikut: (1) Amati bahwa pilihan pada setiap posisi bergantung pada apa yang dapat dicapai sebelumnya — ini menandakan DP. (2) Definisikan keadaannya: dp[i] = apakah kita dapat memecah s[:i]? (3) Nyatakan relasi rekurensi dan kasus dasar sebelum menulis kode. (4) Tulis solusi O(n²) terlebih dahulu, lalu sebutkan pengoptimalan pohon awalan sebagai tindak lanjut. (5) Bahas kasus tepi: untaian karakter kosong, satu karakter, dan kata yang tidak ada dalam kamus.
# Clean final solution to present in interview
def word_break(s, word_dict):
'''O(n^2 * L) time, O(n + W) space where W = total word length in dict'''
word_set = set(word_dict) # O(W) space
n = len(s)
dp = [False] * (n + 1) # O(n) space
dp[0] = True
for i in range(1, n + 1):
for j in range(i): # try all split points
if dp[j] and s[j:i] in word_set:
dp[i] = True
break
return dp[n]
# Time: O(n^2 * L) - n^2 pairs, each dict lookup is O(L)
# Space: O(n) for dp array, O(W) for word_set
print(word_break('applepenapple', ['apple', 'pen'])) # TruePemeriksaan Singkat
Uji pemahaman Anda tentang konsep Struktur Data & Algoritma — Persiapan Wawancara Pemrograman dari pelajaran ini.
Ringkasan Pelajaran
Pada pelajaran ini Anda mempelajari: dp[i] menyatakan apakah s[:i] dapat dipecah menjadi kata-kata dalam kamus, relasi rekurensi O(n²) memeriksa semua titik pemisahan j saat dp[j]=True dan s[j:i] terdapat dalam kumpulan kata, dan pohon awalan dapat mempercepat perulangan dalam dengan memangkas prefiks yang tidak ada lebih awal. Selanjutnya kita membahas Cara Mendekode dan Penghitungan Jalur, pola DP 1D lain yang menyerupai Fibonacci.
Pertanyaan yang Sering Diajukan
Apakah pelajaran “Word Break dan Segmentasi String” gratis?
Ya — teks lengkap “Word Break dan Segmentasi String” 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 “Word Break dan Segmentasi String”?
Gunakan tabel DP 1D untuk menentukan apakah string dapat dipecah menjadi kata-kata kamus, analisis waktu O(n²), dan pahami alasan trie dapat mempercepatnya. 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 3 dari 4.
Berapa lama pelajaran “Word Break dan Segmentasi String” 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
- House Robber: Rekurensi Ambil atau Lewati
- Subarray Maksimum dan Subarray Produk Maksimum
- Word Break dan Segmentasi String
- Decode Ways dan Penghitungan Jalur