0Pricing
DSA Interview Prep · Lección

Word Break y segmentación de strings

Use una tabla de DP 1D para determinar si un string puede segmentarse en palabras del diccionario, analice el tiempo O(n²) y comprenda por qué un trie lo acelera.

Word Break y segmentación de strings es una lección gratuita de DSA Interview Prep en CoddyKit. Esta es la lección 3 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de DSA Interview Prep, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de DSA Interview Prep incluye 4 lecciones en total.

El problema Word Break

Word Break (LeetCode 139) plantea lo siguiente: dada una cadena s y un diccionario de palabras, determine si s puede segmentarse en una secuencia de una o más palabras del diccionario separadas por espacios. Por ejemplo, con s = 'leetcode' y wordDict = ['leet', 'code'], la respuesta es True porque 'leet' + 'code' = 'leetcode'. Este es un problema clásico de DP unidimensional.

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')

Formulación y estado de la DP

Defina dp[i] como True si la subcadena s[:i] puede segmentarse utilizando el diccionario. El caso base es dp[0] = True (la cadena vacía siempre se puede segmentar). Para cada posición i, compruebe todas las posiciones j < i: si dp[j] es True y s[j:i] está en el diccionario, entonces dp[i] = True. La respuesta final es 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

Recorrido de la tabla de DP

Para s = 'leetcode' y el diccionario {'leet', 'code'}: dp[0]=T. En i=4: j=0, dp[0]=T y s[0:4]='leet' está en el diccionario → dp[4]=T. En i=8: j=4, dp[4]=T y s[4:8]='code' está en el diccionario → dp[8]=T. Todas las demás posiciones en las que no termina ninguna palabra permanecen en False. La respuesta dp[8]=True confirma que la cadena se puede segmentar.

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'])

Análisis de la complejidad temporal

La DP ingenua se ejecuta en O(n²): n iteraciones externas multiplicadas por hasta n iteraciones internas. Sin embargo, dividir en fragmentos mediante s[j:i] también cuesta O(n), por lo que la complejidad real en Python es O(n³). Una optimización consiste en recorrer las palabras del diccionario y comprobar si cada palabra termina en la posición i, lo que produce O(n × W × L), donde W es el tamaño del diccionario y L es la longitud media de las palabras. Para la mayoría de las entradas de entrevistas técnicas, O(n²) u O(n³) es aceptable.

# 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

Alternativa de recursión con memoización

El mismo problema puede resolverse de arriba abajo con memoización. Defina una función recursiva can_break(start) que devuelva True si s[start:] se puede segmentar. Pruebe cada palabra como prefijo de s[start:] y aplique recursión al resto. Almacene los resultados en caché para evitar volver a explorar el mismo índice inicial varias veces. Esto equivale a la DP de abajo arriba, pero en la práctica puede ser más rápido si muchas posiciones se descartan pronto.

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

Devolver todas las segmentaciones válidas

Word Break II (LeetCode 140) solicita todas las segmentaciones posibles. El enfoque consiste en utilizar backtracking con memoización: aplique recursión desde cada posición y, cuando una palabra coincida, aplique recursión al resto. Almacene todos los resultados parciales como listas de cadenas. Para evitar un TLE, memorice la lista de oraciones posibles desde cada índice inicial. El número de oraciones puede ser exponencial en el peor caso, pero la memoización elimina los cálculos redundantes.

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']

Optimización con Trie

Cuando el diccionario es grande o las palabras son largas, comprobar s[j:i] in word_set para todos los valores de j es lento debido al hashing de cadenas de Python. Un Trie permite recorrerlo carácter a carácter y descartar pronto las rutas imposibles. En lugar de comprobar las O(n) posiciones iniciales, solo se siguen las rutas que existen en el Trie. Esto reduce significativamente el tiempo de ejecución en la práctica cuando pocos prefijos conducen a palabras válidas.

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

Casos límite y restricciones

Casos límite importantes: (1) Cadena vacía: devuelva True (la cadena vacía se puede segmentar trivialmente). (2) Palabra que no está en el diccionario: dp nunca establece en True la posición correspondiente y devuelve False correctamente. (3) Palabras superpuestas: por ejemplo, 'a' y 'aa' en el diccionario con s='aaa'; la DP gestiona esto de forma natural al comprobar todos los valores de j. (4) Caracteres repetidos: s='aaaaab' con dict=['a','aa','aaa']; hay rutas exponenciales, pero la memoización las limita a 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)

Generalización de la segmentación de cadenas

Word Break se generaliza a cualquier problema de segmentación de cadenas: ¿se puede particionar la cadena s de acuerdo con alguna regla? Sustituya la búsqueda en el diccionario por cualquier comprobación O(1) u O(L). Por ejemplo: ¿se puede particionar s en palíndromos? Utilice una tabla de palíndromos precalculada en lugar de un conjunto de palabras. La estructura de la DP es idéntica; solo cambia la comprobación de validez.

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)

Enfoque de DP frente a BFS

Word Break también puede plantearse como un problema de búsqueda en anchura del camino más corto: cada posición de la cadena es un nodo y existe una arista de j a i si s[j:i] está en el diccionario. Aplicar BFS desde el nodo 0 permite comprobar si se puede llegar al nodo n. BFS ofrece la misma complejidad O(n² × L), pero puede resultar más intuitivo si durante una entrevista lo modela como un problema de grafos.

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

Estrategia de comunicación en entrevistas

En una entrevista, explique el siguiente proceso mental: (1) Observe que las decisiones en cada posición dependen de lo que era alcanzable anteriormente; esto indica que se trata de DP. (2) Defina el estado: dp[i] = ¿se puede segmentar s[:i]? (3) Exponga la recurrencia y el caso base antes de escribir código. (4) Escriba primero la solución O(n²) y, después, mencione la optimización con Trie como posible ampliación. (5) Analice los casos límite: cadena vacía, un solo carácter y palabra que no está en el diccionario.

# 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

Comprobación rápida

Ponga a prueba su comprensión de los conceptos de Data Structures & Algorithms — Coding Interview Prep de esta lección.

Resumen de la lección

En esta lección aprendió: dp[i] representa si s[:i] se puede segmentar en palabras del diccionario, la recurrencia O(n²) comprueba todos los puntos de división j donde dp[j]=True y s[j:i] está en el conjunto de palabras y un Trie puede acelerar el bucle interno al descartar pronto los prefijos inexistentes. A continuación, exploraremos Decode Ways y Counting Paths, otro patrón de DP unidimensional similar al de Fibonacci.

Preguntas frecuentes

¿La lección «Word Break y segmentación de strings» es gratis?

Sí — el texto completo de «Word Break y segmentación de strings» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de DSA Interview Prep, actualiza a CoddyKit PRO. El curso de DSA Interview Prep incluye 4 lecciones en total.

¿Qué aprenderé en «Word Break y segmentación de strings»?

Use una tabla de DP 1D para determinar si un string puede segmentarse en palabras del diccionario, analice el tiempo O(n²) y comprenda por qué un trie lo acelera. Practicas DSA Interview Prep con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.

¿Necesito experiencia previa para empezar DSA Interview Prep?

No se requiere experiencia previa. DSA Interview Prep en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 3 de 4.

¿Cuánto tiempo toma la lección «Word Break y segmentación de strings»?

La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.

¿Puedo escribir y ejecutar código en esta lección de DSA Interview Prep?

Sí. Cada lección de DSA Interview Prep incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.

Todas las lecciones de este curso

  1. House Robber: recurrencia de tomar u omitir
  2. Subarray máximo y subarray de producto máximo
  3. Word Break y segmentación de strings
  4. Decode Ways y conteo de rutas
← Volver a DSA Interview Prep