TL;DR
- El Problema: Optimización de complejidad temporal y espacial para estructuras de datos clave.
- El Enfoque: Comprueba si s2 es una rotación de s1 con una sola llamada a isSubstring: concatena s1 consigo misma y pregunta si s2 vive dentro. Recorrido en Java para principiantes.
- Complejidad: Relación óptima de tiempo y espacio con gestión de casos límite.
Un collar circular de cuentas con letras. Lo abres entre dos cuentas, giras el aro para que otra cuenta quede al frente y lo cierras otra vez. Las cuentas son las mismas, en el mismo orden cíclico. Solo cambió el punto de inicio. Eso es una rotación de cadena.
Este post es el problema 1.9 de la serie CTCI en Java: dadas dos cadenas, decide si una es rotación de la otra, y solo puedes llamar a isSubstring una vez.
El problema en palabras simples
Recibes dos cadenas, s1 y s2.
- Una rotación de
s1significa: eliges un índicei, tomas el sufijos1[i..]y pegas después el prefijos1[0..i). Ejemplo:waterbottlerotada después dewatquedaerbottlewat. - Te dan un helper
isSubstring(big, small)que devuelve true cuandosmallaparece en algún sitio dentro debig. - Escribe
isRotation(s1, s2)que solo devuelve true cuandos2es alguna rotación des1. - Restricción que importa en la entrevista: llama a
isSubstringcomo máximo una vez.
Asume caracteres sensibles a mayúsculas. "Abc" no es rotación de "bca".
Cómo pensar antes de codificar
Fuerza bruta (no la presentes como la respuesta final)
Para cada corte i de 0 a n-1, construye s1.substring(i) + s1.substring(0, i) y compáralo con s2. Son O(n) candidatos, cada comparación O(n), o sea O(n²) y muchas cadenas temporales. Además no usa la regla de una sola llamada.
La idea que desbloquea el límite de una llamada
Si s2 es rotación de s1, entonces s1 se parte como x + y y s2 es y + x para algunas cadenas x e y (pueden ser vacías).
Concatena s1 consigo misma:
s1 + s1 = x + y + x + y
El trozo del medio es y + x, exactamente s2. Así que toda rotación de s1 es subcadena de s1 + s1.
En la otra dirección hace falta un guardia más: las longitudes deben coincidir. Si no, una cadena más corta podría aparecer dentro del texto duplicado sin ser una rotación de igual longitud.
La comprobación completa es:
- Misma longitud (y normalmente no nulas).
isSubstring(s1 + s1, s2)una sola vez.
Cadena vacía: ambas vacías tienen la misma longitud, "" + "" es "", e isSubstring("", "") debería ser true. Una vacía y otra no fallan por longitud.
Solución en Java
/**
* Devuelve true si s2 es rotación de s1, con como máximo una llamada a isSubstring.
* Ejemplo: "waterbottle" y "erbottlewat" -> true.
*/
public static boolean isRotation(String s1, String s2) {
if (s1 == null || s2 == null) {
return false;
}
// Las rotaciones conservan la longitud. Distinta longitud: imposible.
if (s1.length() != s2.length()) {
return false;
}
// Opcional: dos cadenas vacías son rotaciones iguales.
// s1 + s1 sigue vacía; isSubstring debe devolver true para vacío en vacío.
String doubled = s1 + s1;
return isSubstring(doubled, s2);
}
/**
* True si small aparece dentro de big. En la entrevista esto es "dado".
* En Java real puedes implementarlo con indexOf.
*/
public static boolean isSubstring(String big, String small) {
if (big == null || small == null) {
return false;
}
return big.indexOf(small) != -1;
}
Recorre el ejemplo clásico:
| Paso | Valor |
|---|---|
s1 |
waterbottle |
s2 |
erbottlewat |
| longitudes | ambas 11, OK |
s1 + s1 |
waterbottlewaterbottle |
isSubstring |
encuentra erbottlewat después de wat |
Una llamada. Listo.
Complejidad
| Coste | Por qué | |
|---|---|---|
| Tiempo | O(n) típico | Construir s1+s1 es O(n). indexOf es O(n) de media / O(n·m) en el peor caso ingenuo. En entrevista: trabajo lineal en la longitud con una búsqueda de subcadena decente. |
| Espacio extra | O(n) | La cadena duplicada tiene longitud 2n. |
En el peor caso tienes que leer ambas cadenas, así que el orden lineal es el adecuado.
Casos límite que tocan en la entrevista
- Entradas nulas. Devuelve false (o lanza si tu contrato lo dice). Di la elección en voz alta.
- Longitudes distintas. False rápido. No hace falta llamar a
isSubstring(cero llamadas sigue cumpliendo "como máximo una"). - Cadenas idénticas. Rotación por cero.
s1+s1contienes1. True. - Cadenas vacías. Ambas vacías: true. Una vacía: false por longitud.
- Un solo carácter.
"a"y"a"true;"a"y"b"false. - Letras repetidas.
"aaaa"y"aaaa"true."aaba"y"abaa"true (rotación). Usa el test de la cadena duplicada; no inventes casos especiales. - Mayúsculas y espacios.
"Ab"no es rotación de"bA"salvo que el problema ignore mayúsculas. Por defecto, coincidencia exacta. - Llamar a isSubstring más de una vez. Es el núcleo de la pregunta. Construir todas las rotaciones a mano falla el espíritu aunque sea correcto.
Errores habituales
- Olvidar la comprobación de longitud y solo probar
isSubstring(s1+s1, s2). Una cadena más corta que aparece en la fuente duplicada puede colarse. - Llamar a
isSubstringen un bucle sobre puntos de corte. Eso quema el presupuesto. - Usar
containssobres2+s2en lugar des1+s1sin cuidar los papeles. La cadena duplicada debe ser la original (o cualquiera si las longitudes coinciden y son rotaciones mutuas). Quédate con una historia: duplicas1, buscas2. - Ordenar ambas cadenas. Eso comprueba anagrama, no rotación.
"abcd"y"acbd"son anagramas, no rotaciones.
Resumen para contárselo a un amigo
Una rotación es el mismo collar circular de caracteres, abierto en otro cierre.
Si s2 es de verdad rotación de s1, entonces s2 es algún y + x mientras s1 es x + y. Escribe s1 dos veces seguidas y ese y + x queda en el medio. Así que comprueba misma longitud y pregunta una sola vez: ¿es s2 subcadena de s1 + s1?
Ese es todo el truco. Una observación buena gana a un nido de bucles.
Práctica
- Codifica
isRotationde memoria, sin mirar. - Traza en papel
isRotation("waterbottle", "erbottlewat"). - Traza un caso falso:
isRotation("waterbottle", "bottlewaterx")(longitud) eisRotation("abc", "acb")(anagrama, no rotación). - Explica por qué ordenar ambos lados es la herramienta equivocada.
Con esto cierra el Capítulo 1 (Arrays and Strings). Siguiente: listas enlazadas con Remove Dups. Mapa de la serie: CTCI en Java.
