Descripción del Título
Dada una cadena no vacía s y una lista wordDict que contiene palabras no vacías, determine si s se puede dividir por espacios en una o más palabras que aparecen en el diccionario.
Descripción:
Las palabras del diccionario se pueden reutilizar al dividir.
Puede suponer que no hay palabras repetidas en el diccionario.
Ejemplo 1:
Entrada: s = "leetcode", wordDict = ["leet", "code"]
Salida: verdadero
Explicación: Devuelve verdadero porque "leetcode" se puede dividir en "leet code".
Ejemplo 2:
Entrada: s = "applepenapple", wordDict = ["apple", "pen"]
Salida: true
Explicación: Devuelve true porque "applepenapple" se puede dividir en "apple pen apple".
Tenga en cuenta que puede reutilizar palabras en el diccionario.
Ejemplo 3:
Entrada: s = "catsandog", wordDict = ["gatos", "perro", "arena", "y", "gato"]
Salida: falso
Ideas para resolver problemas
(1) pensamiento DFS
- Método DFS sin búsqueda de memoria
Si "leetcode" puede romperse se puede dividir en:
- Si "l" es una palabra en la lista de palabras y si las subcadenas restantes pueden romperse.
- Si "le" es una palabra en la lista de palabras y si las subcadenas restantes pueden romperse.
- "Lee" ... y así sucesivamente ...
Retrocediendo con DFS para examinar todas las posibles divisiones, el puntero escanea de izquierda a derecha:
- Si la parte izquierda del puntero es una palabra, las subcadenas restantes se examinan de forma recursiva.
- Si la parte izquierda del puntero no es una palabra, no la lea, vuelva atrás y examine otras ramas.
Árbol recursivo, el árbol espacial de la solución del problema
-
Método DFS con búsqueda de memoria
El puntero de inicio representa el estado del nodo. Como puede ver, se han realizado muchos cálculos repetidos:
Utilice una matriz para almacenar el resultado del cálculo. El índice de la matriz es la posición del puntero y el valor es el resultado del cálculo. La próxima vez que se encuentre con el mismo subproblema, devolverá directamente el valor almacenado en caché en la matriz sin ingresar recursividad repetida.
Código:
// BFS
public boolean wordBreak(String s, List<String> wordDict) {
Queue<Integer> queue = new LinkedList<>();
queue.add(0);
int slength = s.length();
boolean[] visited = new boolean[slength + 1];
while (!queue.isEmpty()) {
int size = queue.size();
for (int i = 0; i < size; i++) {
int start = queue.poll().intValue();
for (String word : wordDict) {
int nextStart = start + word.length();
if (nextStart > slength || visited[nextStart]) {
continue;
}
if (s.indexOf(word, start) == start) {
if (nextStart == slength) {
return true;
}
queue.add(nextStart);
visited[nextStart] = true;
}
}
}
}
return false;
}
(2) BFS
1. BFS con acceso a nodos duplicados
- Hace un momento usamos DFS para recorrer el árbol espacial y, por supuesto, también se puede usar BFS.
- Mantener una cola, seguir usando punteros para describir un nodo y seguir observando los punteros.
- Al principio, el puntero 0 entra en la columna y luego sale de la columna. Los punteros 1, 2, 3, 4, 5, 6, 7, 8 son sus nodos secundarios y las subcadenas de prefijo se encierran con 0 respectivamente. Si no es una palabra, el puntero correspondiente No ingrese a la lista, de lo contrario ingrese a la lista, continúe investigando las subcadenas restantes a partir de ella.
- Luego repita: el nodo (puntero) se quita de la cola, y sus nodos secundarios se inspeccionan, y los que se pueden ingresar se ponen en cola y se quitan de la cola nuevamente ...
- Hasta que el puntero cruza el límite, no quedan subcadenas y no se pueden ingresar punteros. Si la subcadena de prefijo es una palabra, significa que la palabra se ha cortado antes y se devuelve verdadero.
- Si todo el proceso BFS nunca devuelve verdadero, se devuelve falso.
2. BFS evita visitar nodos duplicados
El DFS sin podar atravesará los nodos repetidamente, y lo mismo es cierto para el BFS. Considere el caso del tiempo de espera, cómo BFS visita repetidamente un nodo.
Solución: Utilice un arreglo visitado para registrar los nodos visitados. Cuando salga a inspeccionar un puntero, omítalo si existe en visitado, de lo contrario guárdelo en visitado.
Código:
// BFS
public boolean wordBreak(String s, List<String> wordDict) {
Queue<Integer> queue = new LinkedList<>();
queue.add(0);
int slength = s.length();
boolean[] visited = new boolean[slength + 1];
while (!queue.isEmpty()) {
int size = queue.size();
for (int i = 0; i < size; i++) {
int start = queue.poll().intValue();
for (String word : wordDict) {
int nextStart = start + word.length();
if (nextStart > slength || visited[nextStart]) {
continue;
}
if (s.indexOf(word, start) == start) {
if (nextStart == slength) {
return true;
}
queue.add(nextStart);
visited[nextStart] = true;
}
}
}
}
return false;
}
(3) Planificación dinámica
- ¿Se puede descomponer la cadena s en palabras en el vocabulario, es decir, si la primera cadena de caracteres de longitud s se puede descomponer en palabras en el vocabulario?
- El gran problema se divide en subproblemas más pequeños. La diferencia de escala es la longitud. El problema se divide en:
- ¿Se puede descomponer la subcadena de los primeros i caracteres en palabras?
- Si la subcadena restante es una sola palabra.
- dp [i]: si la subcadena s [0: i-1] de longitud i se puede dividir en palabras. El título requiere que preguntemos: dp [s.length]
Ecuación de transición de estado
- Usamos el puntero j para dividir la subcadena de s [0: i], como se muestra en la siguiente figura:
- Si dp [i + 1] de la subcadena de s [0: i] es verdadero (si se puede dividir en palabras) depende de dos puntos:
- Si dp [j] de su subcadena de prefijo s [0: j-1] es verdadero.
- Si la subcadena restante s [j: i] es una sola palabra.
caso base
- dp [0] = verdadero. La duración de 0 s [0: -1] se puede dividir en palabras de lista de palabras.
- Parece absurdo, pero esto es solo para hacer que las condiciones de frontera también satisfagan la ecuación de transición de estado.
- Cuando j = 0 (la parte amarilla en la figura anterior es una cadena vacía y la cadena de prefijo dividida por j es una cadena vacía), el dp [i + 1] de la subcadena de s [0: i] depende de la valor de s [0: -1] dp [0], y si la subcadena restante s [0: i] es una sola palabra.
- Solo si dp [0] es verdadero, dp [i + 1] solo dependerá de si s [0: i] es una sola palabra y satisface la ecuación de transición de estado.
Programación dinámica después de la optimización
- En el proceso iterativo, si se encuentra dp [i] == true, rompa directamente
- Si dp [j] == falso, no hay posibilidad de que dp [i] sea verdadero, continúe y examine el siguiente j
Código:
// DP
public boolean wordBreak(String s, List<String> wordDict) {
int maxWordLength = 0;
Set<String> wordSet = new HashSet<>(wordDict.size());
for (String word : wordDict) {
wordSet.add(word);
if (word.length() > maxWordLength) {
maxWordLength = word.length();
}
}
boolean[] dp = new boolean[s.length() + 1];
dp[0] = true;
for (int i = 1; i < dp.length; i++) {
for (int j = (i - maxWordLength < 0 ? 0 : i - maxWordLength); j < i; j++) {
if (dp[j] && wordSet.contains(s.substring(j, i))) {
dp[i] = true;
break;
}
}
}
return dp[dp.length - 1];
}
Referencia de imagen: fuente de imágenes en el texto