Arrays en Java — ejercicios con invertir, rotar, quitar duplicados y fusionar (y un error que se esconde donde menos lo esperas)
Cuando hice mi primer ejercicio con matrices, lo probé con una matriz cuadrada de 3×3 y funcionó a la primera. Contentísimo. Después lo probé con una de 2 filas y 4 columnas y el programa petó: había usado tabla.length donde tocaba tabla[0].length. Con una matriz cuadrada, las dos cosas valen lo mismo, así que el fallo se quedó escondido hasta que probé otra forma. Desde entonces pruebo siempre con más de un caso, y verás que en estos ejercicios hay varios casos «incómodos» a propósito.
Aquí tienes tres ejercicios de arrays resueltos y comentados, y al final una sección de «¿Qué falla aquí?» con tres fragmentos con error para que los cacemos. Si todavía no has visto la teoría o la práctica, conviene pasar antes por ellas.
Cómo sacarle partido: lee el enunciado, intenta resolverlo tú en tu editor y solo entonces mira la solución. Aunque no te salga, el intento hace que la solución se entienda diez veces mejor. Y cuando termines, prueba tu código con casos raros: array vacío, un solo elemento, valores repetidos.
Tabla de Contenidos
Ejercicio 1: invertir y rotar un array (sin tocar el original)
Enunciado. Escribe dos métodos:
invertir(int[] a): devuelve un array nuevo con los mismos elementos en orden inverso.rotar(int[] a, int k): devuelve un array nuevo con los elementos desplazadoskposiciones a la derecha. Los que salen por la derecha vuelven a entrar por la izquierda. Sikes negativo, se rota a la izquierda.
En los dos casos, el array original no debe cambiar.
Ejemplo. Con {1, 2, 3, 4, 5}: invertido es {5, 4, 3, 2, 1}; rotado 2 posiciones a la derecha es {4, 5, 1, 2, 3}.
Solución comentada
static int[] invertir(int[] a) {
int[] r = new int[a.length]; // array NUEVO: el original no se toca
for (int i = 0; i < a.length; i++) {
r[i] = a[a.length - 1 - i]; // el primero de r es el último de a, etc.
}
return r;
}
// Rota k posiciones a la derecha (k negativo = a la izquierda)
static int[] rotar(int[] a, int k) {
int n = a.length;
int[] r = new int[n];
if (n == 0) {
return r; // evita dividir entre cero con %
}
int desplazamiento = ((k % n) + n) % n; // lo deja siempre entre 0 y n-1
for (int i = 0; i < n; i++) {
r[(i + desplazamiento) % n] = a[i]; // el que sale por la derecha entra por la izquierda
}
return r;
}
Por qué funciona
En invertir: la clave es la posición simétrica. En un array de longitud n, el elemento de la posición i tiene su «opuesto» en n - 1 - i. Con 5 elementos: la 0 se empareja con la 4, la 1 con la 3 y la 2 consigo misma. Y como escribimos en r, que es un array nuevo, el original queda intacto.
En rotar: cada elemento de la posición i pasa a la posición i + desplazamiento. Si esa suma se sale del array, el operador % (resto) la devuelve al principio: es lo que hace que lo que sale por la derecha entre por la izquierda. Probemos con {1, 2, 3, 4, 5} y desplazamiento 2: el 1 (posición 0) va a la 2, el 4 (posición 3) va a (3 + 2) % 5 = 0, y el 5 va a (4 + 2) % 5 = 1.
Casos de prueba
int[] notas = {1, 2, 3, 4, 5};
System.out.println("Original: " + Arrays.toString(notas));
System.out.println("Invertido: " + Arrays.toString(invertir(notas)));
System.out.println("Rotado 2 (der.): " + Arrays.toString(rotar(notas, 2)));
System.out.println("Rotado -1 (izq.):" + Arrays.toString(rotar(notas, -1)));
System.out.println("Rotado 7: " + Arrays.toString(rotar(notas, 7)));
System.out.println("Original: " + Arrays.toString(notas));
System.out.println("Vacío invertido: " + Arrays.toString(invertir(new int[0])));
Original: [1, 2, 3, 4, 5] Invertido: [5, 4, 3, 2, 1] Rotado 2 (der.): [4, 5, 1, 2, 3] Rotado -1 (izq.):[2, 3, 4, 5, 1] Rotado 7: [4, 5, 1, 2, 3] Original: [1, 2, 3, 4, 5] Vacío invertido: []
Fíjate en tres cosas: rotar 7 da lo mismo que rotar 2 (porque 7 % 5 = 2), rotar -1 lleva el primero al final, y el original sale igual al principio y al final.
Preguntas que te puedes hacer
¿Por qué ((k % n) + n) % n y no simplemente k % n? Porque en Java el resto de un número negativo es negativo: -1 % 5 da -1, no 4, y una posición negativa daría ArrayIndexOutOfBoundsException. Sumar n y volver a hacer % n lo convierte siempre en un valor entre 0 y n - 1.
¿Por qué comprobamos n == 0 primero? Porque k % 0 es una división entre cero y lanza ArithmeticException. Un array vacío es un caso límite que siempre conviene contemplar.
¿No se podría invertir «en el sitio», intercambiando elementos? Sí, y es más eficiente en memoria, pero modifica el original. Para este enunciado (que pide no tocarlo), la copia es lo correcto. Más abajo verás qué pasa cuando no se hace así.
Escribir int[] r = a; pensando que copia el array. Solo copia la dirección: r y a serían el mismo array y al invertir «en r» estarías modificando a. Para una copia de verdad, new int[...] o Arrays.copyOf.
Ejercicio 2: eliminar duplicados
Enunciado. Escribe sinDuplicados(int[] a), que devuelve un array nuevo con los valores de a sin repeticiones, respetando el orden de la primera aparición de cada uno.
Ejemplo. Con {4, 7, 4, 0, 7, 7, 9, 0} devuelve {4, 7, 0, 9}.
Un array no puede cambiar de tamaño, y no sabes cuántos valores distintos habrá hasta recorrerlo. Piensa en crear primero un array «temporal» lo bastante grande y recortarlo al final con Arrays.copyOf.
Solución comentada
// ¿Está valor entre las primeras 'hasta' posiciones de a?
static boolean contiene(int[] a, int hasta, int valor) {
for (int i = 0; i < hasta; i++) {
if (a[i] == valor) {
return true;
}
}
return false;
}
static int[] sinDuplicados(int[] a) {
int[] temporal = new int[a.length]; // como mucho caben todos
int cuantos = 0; // cuántos valores distintos llevamos
for (int n : a) {
if (!contiene(temporal, cuantos, n)) { // solo miramos lo ya rellenado
temporal[cuantos] = n;
cuantos++;
}
}
return Arrays.copyOf(temporal, cuantos); // recortamos al tamaño real
}
Por qué funciona
Recorremos a y, para cada valor, miramos si ya lo hemos guardado en temporal. Si no está, lo añadimos en la siguiente posición libre (cuantos) y avanzamos el contador. Al terminar, temporal tiene los valores distintos al principio y ceros de relleno al final, así que Arrays.copyOf(temporal, cuantos) se queda solo con la parte buena.
La clave de todo el ejercicio está en el parámetro hasta. Fíjate en que contiene no recorre todo temporal, solo las primeras cuantos posiciones. ¿Por qué? Porque el resto del array todavía no se ha rellenado y contiene ceros por defecto.
Casos de prueba
int[] repetidos = {4, 7, 4, 0, 7, 7, 9, 0};
System.out.println("Con duplicados: " + Arrays.toString(repetidos));
System.out.println("Sin duplicados: " + Arrays.toString(sinDuplicados(repetidos)));
System.out.println("Sin duplicados (con ceros): " + Arrays.toString(sinDuplicados(new int[]{0, 0, 0})));
Con duplicados: [4, 7, 4, 0, 7, 7, 9, 0] Sin duplicados: [4, 7, 0, 9] Sin duplicados (con ceros): [0]
Preguntas que te puedes hacer
¿Qué pasaría si contiene recorriera todo temporal? Que los ceros «de relleno» se tomarían por valores ya guardados. Con {0, 0, 0} el programa creería que el 0 ya está desde el principio y devolvería un array vacío en vez de [0]. Por eso el caso de prueba con ceros es importante: es justo donde el fallo se destapa.
¿Por qué no usar Arrays.sort y quitar los consecutivos? Se puede, y es más eficiente, pero perdería el orden de aparición que pide el enunciado (y además modificaría el original si no se trabaja con una copia).
¿Qué coste tiene esta solución? Para cada elemento recorremos los ya guardados, así que en el peor caso (todos distintos) hace del orden de n² comparaciones. Para los tamaños de estos ejercicios no importa, pero con millones de datos habría que usar otras estructuras (más adelante verás HashSet).
Este patrón de «array temporal grande + copyOf al final» es la forma estándar de construir resultados cuyo tamaño no conoces de antemano, hasta que llegues a ArrayList.
Ejercicio 3: fusionar dos arrays ordenados
Enunciado. Escribe fusionar(int[] a, int[] b), que recibe dos arrays ya ordenados de menor a mayor y devuelve un array nuevo con todos sus elementos, también ordenado. No se puede volver a ordenar todo desde cero: hay que aprovechar que ya vienen ordenados.
Ejemplo. Con {1, 4, 6, 9} y {2, 3, 7, 8, 10, 12} devuelve {1, 2, 3, 4, 6, 7, 8, 9, 10, 12}.
Imagina dos montones de cartas ordenadas boca arriba. Siempre coges la menor de las dos que están encima. Para saber «cuál es la de encima» de cada montón necesitas una posición en cada array: i para a y j para b. ¿Y qué haces cuando un montón se acaba?
Solución comentada
static int[] fusionar(int[] a, int[] b) {
int[] r = new int[a.length + b.length];
int i = 0; // posición en a
int j = 0; // posición en b
int k = 0; // posición en r
while (i < a.length && j < b.length) { // mientras queden de los dos
if (a[i] <= b[j]) {
r[k] = a[i];
i++;
} else {
r[k] = b[j];
j++;
}
k++;
}
while (i < a.length) { // sobras de a (si las hay)
r[k] = a[i];
i++;
k++;
}
while (j < b.length) { // sobras de b (si las hay)
r[k] = b[j];
j++;
k++;
}
return r;
}
Por qué funciona
Con tres posiciones (i, j, k) avanzamos por los tres arrays a la vez. En cada vuelta comparamos los dos elementos de «arriba» (a[i] y b[j]), copiamos el menor a r y avanzamos solo en el array del que lo sacamos. Cuando uno de los dos se agota, el primer bucle termina; uno de los dos bucles siguientes copia lo que sobra del otro (y el otro no hace nada, porque su condición ya es falsa).
Por qué las «sobras» se copian tal cual sin comparar: como el array que queda ya está ordenado y todo lo que contiene es mayor o igual que lo ya copiado, puede pegarse directamente al final.
Casos de prueba
int[] a = {1, 4, 6, 9};
int[] b = {2, 3, 7, 8, 10, 12};
System.out.println("Fusión: " + Arrays.toString(fusionar(a, b)));
System.out.println("Con vacío: " + Arrays.toString(fusionar(a, new int[0])));
System.out.println("Con repetidos: " + Arrays.toString(fusionar(new int[]{1, 3, 3}, new int[]{3, 4})));
Fusión: [1, 2, 3, 4, 6, 7, 8, 9, 10, 12] Con vacío: [1, 4, 6, 9] Con repetidos: [1, 3, 3, 3, 4]
Preguntas que te puedes hacer
¿Por qué <= y no < al comparar? Con <=, si hay un empate gana a, y el resultado sale igual de ordenado. Con < también funcionaría; cambia solo de qué array sale primero un valor repetido. Lo importante es que la condición cubra el caso de igualdad en uno de los dos lados.
¿Qué pasa si uno de los arrays está vacío? El primer bucle no se ejecuta ni una vez (porque a.length o b.length es 0) y el bucle de sobras copia el otro entero. Por eso el caso fusionar(a, new int[0]) funciona sin código especial.
¿Por qué no concatenar y ordenar con Arrays.sort? Daría el mismo resultado, pero el enunciado pide aprovechar que ya vienen ordenados. Esta fusión recorre cada array una sola vez, mientras que ordenar desde cero es más costoso. Es, además, el corazón de un algoritmo famoso (merge sort) que verás más adelante.
Cuando un algoritmo tiene varias «posiciones» (aquí i, j, k), ponles nombres que indiquen de qué array son, o un comentario al declararlas, como en la solución. Casi todos los errores de este tipo de ejercicios vienen de avanzar la posición equivocada.
¿Qué falla aquí?
Tres fragmentos de código con un error de arrays cada uno. Antes de leer la explicación, intenta encontrar el fallo.
Fragmento 1: la suma de columnas
static int[] sumaColumnas(int[][] tabla) {
int[] sumas = new int[tabla.length];
for (int f = 0; f < tabla.length; f++) {
for (int c = 0; c < tabla[f].length; c++) {
sumas += tabla[f];
}
}
return sumas;
}
Con la tabla {{1,2,3},{4,5,6},{7,8,9}} imprime [12, 15, 18], perfecto. Pero con {{1,2,3,4},{5,6,7,8}} aparece:
java.lang.ArrayIndexOutOfBoundsException: Index 2 out of bounds for length 2
¿Qué falla? El array sumas se crea con tabla.length (el número de filas), pero necesita una posición por columna. En una matriz cuadrada coinciden y el error se esconde; con 2 filas y 4 columnas, sumas tiene 2 posiciones y al llegar a la columna 2 se sale.
Arreglo: int[] sumas = new int[tabla[0].length];. Con eso, la segunda tabla da [6, 8, 10, 12]. Es exactamente mi anécdota del principio.
Fragmento 2: el invertir que fastidia el original
static int[] invertir(int[] a) {
for (int i = 0; i < a.length / 2; i++) {
int tmp = a[i];
a[i] = a[a.length - 1 - i];
a[a.length - 1 - i] = tmp;
}
return a;
}
// En main:
int[] notas = {1, 2, 3, 4};
int[] inv = invertir(notas);
System.out.println(Arrays.toString(inv) + " " + Arrays.toString(notas));
Imprime [4, 3, 2, 1] [4, 3, 2, 1]: los dos están invertidos.
¿Qué falla? El método intercambia los elementos directamente en el array que recibe (a) y lo devuelve. Como a es la dirección del array original, inv y notas son el mismo array. El método «funciona», pero estropea los datos de quien lo llama.
Arreglo: trabajar sobre una copia, por ejemplo int[] r = Arrays.copyOf(a, a.length);, hacer los intercambios en r y devolver r. Con eso, el resultado es [4, 3, 2, 1] [1, 2, 3, 4].
Fragmento 3: la comparación que nunca acierta
int[] esperado = {1, 2, 3, 4};
int[] obtenido = fusionar(new int[]{1, 3}, new int[]{2, 4});
if (obtenido == esperado) {
System.out.println("Correcto");
} else {
System.out.println("Incorrecto");
}
Imprime Incorrecto, aunque obtenido tiene exactamente [1, 2, 3, 4].
¿Qué falla? == compara direcciones, no contenidos. esperado y obtenido son dos arrays distintos en memoria, aunque contengan lo mismo.
Arreglo: if (Arrays.equals(obtenido, esperado)). Con eso imprime Correcto.
Errores típicos en estos ejercicios
- Escribir
int[] r = a;creyendo que copia. Solo copia la dirección. - Usar
tabla.lengthdonde tocatabla[0].length(o al revés). En matrices cuadradas no se nota. - Olvidar los casos límite: array vacío, un solo elemento, todos los valores iguales, ceros.
- Recorrer todo un array temporal en vez de solo la parte ya rellenada.
- Comparar arrays con
==en lugar deArrays.equals.
Chuletario: arrays en Java
Lo esencial en 30 segundos
- Un array tiene tamaño fijo y un solo tipo. Los índices van de
0alength - 1. lengthen arrays no lleva paréntesis (enStringsí:length()).- Un array es un objeto: la variable guarda una referencia, no los datos.
- Valores por defecto al crear:
0(números),false(boolean),'\u0000'(char),null(objetos).
Crear y recorrer
| Quiero… | Código |
|---|---|
| Crear vacío | int[] a = new int[5]; |
| Crear con valores | int[] a = {5, 2, 9}; |
| Tamaño | a.length |
| Último elemento | a[a.length - 1] |
| Recorrer con índice | for (int i = 0; i < a.length; i++) { … a[i] … } |
| Recorrer sin índice | for (int x : a) { … x … } |
| Rellenar | Arrays.fill(a, 4); → [4, 4, 4] |
i <= a.length. El último índice válido es a.length - 1: si no, ArrayIndexOutOfBoundsException.
Copiar y comparar
| Quiero… | Código | Resultado |
|---|---|---|
| ❌ «Copiar» | int[] m = n; | Dos nombres para el mismo array: m[0] = 1 cambia también n[0] |
| ✅ Copia real | Arrays.copyOf(n, n.length) | Array nuevo e independiente |
| Copia más larga | Arrays.copyOf(n, 5) | [5, 2, 9, 0, 0] (rellena con ceros) |
| Copiar un tramo | Arrays.copyOfRange(n, 1, 3) | [2, 9] (el final no se incluye) |
| ❌ Comparar | a == b | Compara referencias: dos copias iguales dan false |
| ✅ Comparar contenido | Arrays.equals(a, b) | true si tienen los mismos valores en el mismo orden |
modificar el array que recibes en un método (invertir, ordenar…) y descubrir que has cambiado el original. Si quieres devolver otro, haz Arrays.copyOf primero.
Ordenar y buscar (import java.util.Arrays;)
| Quiero… | Código |
|---|---|
| Ordenar (modifica el array) | Arrays.sort(a); |
| Buscar (solo si está ordenado) | Arrays.binarySearch(a, valor) |
| Imprimir | System.out.println(Arrays.toString(a)); |
| Imprimir matriz | Arrays.deepToString(m) |
binarySearchen[1, 2, 9]: buscar2→1(la posición). Buscar5→-3, es decir-(punto de inserción) - 1: negativo = no está.- Imprimir un array con
println(a)muestra algo como[I@1b6d3586, no los datos.
binarySearch sobre un array sin ordenar no da error, da un resultado sin sentido. Ordena antes.
Arrays 2D (matrices)
int[][] t = {{1, 2, 3, 4},
{5, 6, 7, 8}};
t.length // 2 → número de FILAS
t[0].length // 4 → número de COLUMNAS (de la fila 0)
t[1][2] // 7 → fila 1, columna 2
// recorrido completo
for (int f = 0; f < t.length; f++) {
for (int c = 0; c < t[f].length; c++) {
System.out.print(t[f] + " ");
}
System.out.println();
}
- Una matriz es un array de arrays: las filas pueden tener longitudes distintas (
new int[2][]y luegoir[0] = new int[1]; ir[1] = new int[3];). - Para crear una matriz del tamaño de otra: filas =
t.length, columnas =t[0].length.
new int[tabla.length] cuando necesitas un resultado por columna. Con una tabla 2×4 eso crea 2 posiciones, y al escribir en la columna 2 salta ArrayIndexOutOfBoundsException. Lo correcto: new int[tabla[0].length].
Arrays como parámetro y retorno
static int[] doble(int[] a) { // recibe la REFERENCIA
int[] r = new int[a.length]; // resultado nuevo → no tocamos el original
for (int i = 0; i < a.length; i++) r[i] = a[i] * 2;
return r;
}
- Lo que hagas dentro con
a[i] = …sí afecta al array de fuera. Reasignara = new int[3]dentro no.
Plantillas de los ejercicios
Invertir (a otro array):
int[] r = new int[a.length]; for (int i = 0; i < a.length; i++) r[i] = a[a.length - 1 - i];
[1,2,3,4,5] → [5,4,3,2,1]
Rotar k posiciones a la derecha (k puede ser negativo o mayor que n):
int n = a.length; int[] r = new int[n]; int d = ((k % n) + n) % n; // normaliza k a 0..n-1 for (int i = 0; i < n; i++) r[(i + d) % n] = a[i];
rotar(2) → [4,5,1,2,3] · rotar(-1) → [2,3,4,5,1] · rotar(7) = rotar(2)
Quitar duplicados (patrón array + contador):
int[] temp = new int[a.length];
int count = 0;
for (int x : a) {
boolean esta = false;
for (int j = 0; j < count; j++) // solo mira hasta count, no hasta temp.length
if (temp[j] == x) esta = true;
if (!esta) temp[count++] = x;
}
int[] r = Arrays.copyOf(temp, count); // recorta al tamaño real
{4,7,4,0,7,7,9,0} → [4,7,0,9] · {0,0,0} → [0]
mirar temp hasta temp.length en vez de hasta count. Las posiciones sin usar valen 0 y «encuentran» un 0 que nunca añadiste.
Fusionar dos arrays ordenados (dos punteros):
int[] r = new int[a.length + b.length];
int i = 0, j = 0, k = 0;
while (i < a.length && j < b.length)
r[k++] = (a[i] <= b[j]) ? a[i++] : b[j++];
while (i < a.length) r[k++] = a[i++]; // lo que sobre de a
while (j < b.length) r[k++] = b[j++]; // lo que sobre de b
{1,4,6,9} + {2,3,7,8,10,12} → [1,2,3,4,6,7,8,9,10,12]
Errores que más se repiten
| Síntoma | Causa probable |
|---|---|
ArrayIndexOutOfBoundsException: Index 5 out of bounds for length 5 | <= en lugar de <, o índice calculado sin comprobar |
| El array original cambió «solo» | Asignaste m = n o modificaste el parámetro en sitio |
a == b da false con datos idénticos | Compara referencias → Arrays.equals |
Sale [I@… por pantalla | Falta Arrays.toString |
| Ceros «fantasma» en el resultado | Array más grande de lo necesario sin recortar con copyOf(…, count) |
binarySearch devuelve algo absurdo | El array no estaba ordenado |
| Fallo solo con matrices no cuadradas | Confundir t.length con t[0].length |
Antes de entregar, pregúntate
- ¿Mi bucle llega hasta
length - 1y no más? - ¿Estoy modificando el original sin querer?
- ¿Qué pasa con un array vacío? ¿Y de un elemento?
- ¿Funciona en una matriz que no es cuadrada?
Resumen
- Para no tocar el original, trabaja con un array nuevo (
newoArrays.copyOf). - El operador
%permite «dar la vuelta» a un array; con negativos, usa((k % n) + n) % n. - Para resultados de tamaño desconocido: array temporal grande, contador y
Arrays.copyOfal final. - Dos arrays ordenados se fusionan con una posición por array y copiando las sobras al final.
- Prueba siempre con casos distintos: vacío, un elemento, repetidos, matrices no cuadradas.
Checklist: ¿lo has conseguido?
- ¿Has intentado resolver cada ejercicio antes de mirar la solución?
- ¿Sabes explicar por qué
invertirdevuelve un array nuevo? - ¿Entiendes por qué
contienerecibe el parámetrohasta? - ¿Sabes qué ocurre con las «sobras» al fusionar?
- ¿Has encontrado los tres fallos de «¿Qué falla aquí?» sin leer la explicación?
Con esto cierras el bloque de arrays. El siguiente paso en Java son los Strings, que se parecen mucho más a los arrays de lo que parece. ¡Allí te espero!
Sigue aprendiendoTodos los artículos de Java¿Te ha servido este artículo? Así puedes apoyar SergioLearns →
