0Pricing
Coding Interview Prep · Pelajaran

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 Coding 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 Coding Interview Prep, dan progresmu tersinkronisasi di web dan aplikasi CoddyKit. Kursus Coding 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']))  # False

Menelusuri 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']))  # True

Alternatif 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']))  # False

Mengembalikan 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']))  # True

Kasus 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']))  # False

Strategi 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']))  # True

Pemeriksaan 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 Coding Interview Prep, upgrade ke CoddyKit PRO. Kursus Coding 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 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 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 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

  1. House Robber: Rekurensi Ambil atau Lewati
  2. Subarray Maksimum dan Subarray Produk Maksimum
  3. Word Break dan Segmentasi String
  4. Decode Ways dan Penghitungan Jalur
← Kembali ke Coding Interview Prep