Difícil · simulación

Recorrido en espiral

Pela la cuadrícula como una cebolla: escribe la fila superior, luego la columna derecha, luego la inferior al revés, luego la izquierda hacia arriba — y antes de cada una de las dos últimas, pregúntate si queda algo que pelar. Toda la dificultad está en esa pregunta.

En una fraseMantén <code>top/bottom/left/right</code>; barre la fila superior, la columna derecha, luego la inferior al revés y la izquierda hacia arriba — pero solo <em>si</em> aún quedan filas y columnas.

El problema

Entrada: La primera línea trae n y m (1 ≤ n, m ≤ 10). Luego n líneas de m enteros (−100 a 100).

Salida: El recorrido en espiral empezando en la esquina superior izquierda hacia la derecha, valores separados por un espacio en una sola línea.

Ejemplos:

EntradaSalida
3 3
1 2 3
4 5 6
7 8 9
1 2 3 6 9 8 7 4 5
1 4
1 2 3 4
1 2 3 4

El ejemplo de una sola fila está ahí a propósito: tras barrer la fila superior, top sobrepasa bottom, así que el barrido de la fila inferior debe saltarse por completo — o la fila se escribiría dos veces. El segundo ejemplo del juez es el detector de trampas.

Tu solución

main.cpp — tu solución
Entrada personalizada para «Ejecutar mi entrada»

La corrección corre en tu navegador contra pruebas ocultas. Un bucle infinito se corta a los 3 segundos — aun así, asegúrate de que todo bucle tenga salida. Tu código se guarda solo mientras escribes.

¿Atascado? Pistas

Pista 1 — Una vuelta = cuatro barridos

Fila superior izquierda→derecha, columna derecha arriba→abajo, fila inferior derecha→izquierda, columna izquierda abajo→arriba. Tras cada barrido, encoge el borde correspondiente: top++, right--, bottom--, left++. Repite mientras top <= bottom && left <= right — cuando los rangos se cruzan, la cebolla está pelada.

Pista 2 — Las dos re-comprobaciones son el problema

Tras top++, la fila inferior puede ya no existir (cuadrículas de una fila, o espirales que terminan en una franja horizontal) — resguarda el barrido inferior con if (top <= bottom). Simétricamente, el barrido izquierdo con if (left <= right). Sin estas dos, los tests de una fila y una columna fallan por visitas duplicadas.

Pista 3 — Sigue a mano una cuadrícula no cuadrada

Las cuadrículas cuadradas esconden los bugs de borde de maravilla — todo cierra fino. Una de 2×3 y una de 3×2 seguidas una vez en papel exponen cualquier error en los cuatro bucles. Si tu espiral funciona en cuadrados y falla en el juez, este es el motivo.

Solución (¡inténtalo primero!)
#include <iostream>
using namespace std;

int main() {
    int n, m;
    cin >> n >> m;
    int a[12][12];
    for (int i = 0; i < n; i++)
        for (int j = 0; j < m; j++)
            cin >> a[i][j];

    int top = 0, bottom = n - 1, left = 0, right = m - 1;
    bool first = true;
    while (top <= bottom && left <= right) {
        for (int j = left; j <= right; j++) {
            if (!first) cout << " ";
            cout << a[top][j]; first = false;
        }
        top++;
        for (int i = top; i <= bottom; i++) {
            if (!first) cout << " ";
            cout << a[i][right]; first = false;
        }
        right--;
        if (top <= bottom) {
            for (int j = right; j >= left; j--) {
                if (!first) cout << " ";
                cout << a[bottom][j]; first = false;
            }
            bottom--;
        }
        if (left <= right) {
            for (int i = bottom; i >= top; i--) {
                if (!first) cout << " ";
                cout << a[i][left]; first = false;
            }
            left++;
        }
    }
    cout << endl;
    return 0;
}

La simulación con bordes que se encogen es el esqueleto detrás de los problemas de rotación, recorrido y relleno por inundación. Compárala con la otra forma de tratar cuadrículas — reindexar filas enteras en Transponer una matriz — y nota la diferencia: la transposición mueve índices, la espiral mueve *bordes*. La siguiente habilidad tras ambas es preguntar por rectángulos: Sumas por rangos 2D.

Análisis profundo

Por qué funciona

Cuatro bordes (top/bottom/left/right) definen el anillo sin visitar; cada vuelta escribe una revolución completa y encoge un borde por barrido. Las dos re-comprobaciones tras encoger existen porque un anillo puede quedar de una sola fila o columna — escribir su parte inferior y luego la izquierda recorrería las mismas celdas dos veces.

El coste

O(n·m): cada celda se escribe exactamente una vez y la aritmética de bordes es constante por barrido. Espacio O(1) además de la cuadrícula.

Dónde falla

  • Perder la guarda if (top <= bottom) antes del barrido inferior (o la de la izquierda): las cuadrículas de una fila o columna se escriben dos veces — los tests no cuadrados del juez existen justo para esto.
  • Barrer la fila inferior izquierda→derecha o la columna izquierda arriba→abajo: la espiral alterna el sentido, y los bucles invertidos (j--, i--) son obligatorios en la segunda mitad de cada vuelta.
  • Confiar en los tests cuadrados: un 3×3 cierra fino y esconde todos los bugs. Solo un 2×3 y un 3×2 seguidos a mano exponen los cuatro límites de bucle.

Siguiente paso

Manejo de cuadrículas hasta ahora: reindexar filas en Transponer una matriz, combinar cuadrículas en Multiplicación de matrices, caminar bordes aquí. Luego, preguntar por rectángulos: Sumas por rangos 2D.

Dos cosas para recordar

  • Una vuelta = cuatro barridos; tras cada uno, encoge ese borde.
  • Las dos re-comprobaciones (¿quedan filas? ¿quedan columnas?) son lo que compran los tests no cuadrados.
🐞 Esta versión falla: ábrela y pruébala

Sin re-comprobaciones antes de los barridos inferior e izquierdo — las filas únicas se escriben dos veces

#include <iostream>
using namespace std;

int main() {
    int n, m;
    cin >> n >> m;
    int a[12][12];
    for (int i = 0; i < n; i++)
        for (int j = 0; j < m; j++)
            cin >> a[i][j];
    int top = 0, bottom = n - 1, left = 0, right = m - 1;
    bool first = true;
    while (top <= bottom && left <= right) {
        for (int j = left; j <= right; j++) { if (!first) cout << " "; cout << a[top][j]; first = false; }
        top++;
        for (int i = top; i <= bottom; i++) { if (!first) cout << " "; cout << a[i][right]; first = false; }
        right--;
        for (int j = right; j >= left; j--) { if (!first) cout << " "; cout << a[bottom][j]; first = false; }  // bug: no guard
        bottom--;
        for (int i = bottom; i >= top; i--) { if (!first) cout << " "; cout << a[i][left]; first = false; }   // bug: no guard
        left++;
    }
    cout << endl;
    return 0;
}