🔗 ¿Qué es una lista enlazada?
Una lista enlazada (linked list) es una estructura de datos lineal formada por una secuencia de elementos —denominados nodos— en la que cada nodo contiene un dato y una referencia (o puntero) al siguiente nodo de la secuencia. A diferencia de los arrays, los elementos de una lista enlazada no se almacenan en posiciones contiguas de memoria, lo que proporciona una flexibilidad extraordinaria para insertar y eliminar elementos sin necesidad de desplazar los restantes.
Las listas enlazadas constituyen una de las estructuras de datos fundamentales en ciencias de la computación. Comprender su funcionamiento interno resulta imprescindible no solo para resolver problemas algorítmicos, sino también para entender cómo operan internamente colecciones de alto nivel como java.util.LinkedList o las implementaciones de pilas, colas y grafos.
🔹 Representación visual
Una lista enlazada simple con tres elementos se puede representar gráficamente como una cadena de cajas conectadas por flechas:
cabeza
│
▼
┌───────┐ ┌───────┐ ┌───────┐
│ "Ana" │───▶│ "Luis"│───▶│"María"│───▶ null
└───────┘ └───────┘ └───────┘
nodo 1 nodo 2 nodo 3
La referencia cabeza apunta al primer nodo. El último nodo tiene su referencia siguiente establecida a null, lo que indica el final de la lista. Si la cabeza es null, la lista está vacía.
📋 Tipos de listas enlazadas
Existen tres variantes principales de listas enlazadas, cada una con características y casos de uso específicos:
| Tipo | Dirección de enlaces | Ventaja principal | Uso típico |
|---|---|---|---|
| Simple | Solo hacia adelante (siguiente) | Menor consumo de memoria por nodo | Pilas, colas simples, almacenamiento básico |
| Doblemente enlazada | Adelante y atrás (siguiente + anterior) | Recorrido bidireccional, eliminación O(1) | java.util.LinkedList, editores de texto, historial de navegación |
| Circular | El último apunta al primero | No hay final; recorrido cíclico natural | Turnos rotativos, buffers circulares, juegos por turnos |
Simple: [A] ──▶ [B] ──▶ [C] ──▶ null
Doble: null ◀── [A] ◀──▶ [B] ◀──▶ [C] ──▶ null
Circular: [A] ──▶ [B] ──▶ [C] ──┐
▲ │
└──────────────────────┘
📦 El nodo: unidad fundamental
El nodo es el bloque de construcción básico de toda lista enlazada. Cada nodo almacena dos elementos: el dato (la información útil) y una referencia al siguiente nodo. En Java, se implementa como una clase interna con genéricos para máxima reutilización:
/**
* Nodo genérico para una lista enlazada simple.
* @param <T> Tipo de dato almacenado en el nodo
*/
public class Nodo<T> {
private T dato; // Información almacenada
private Nodo<T> siguiente; // Referencia al próximo nodo
// Constructor: crea un nodo con dato y referencia al siguiente
public Nodo(T dato, Nodo<T> siguiente) {
this.dato = dato;
this.siguiente = siguiente;
}
// Constructor: crea un nodo aislado (siguiente = null)
public Nodo(T dato) {
this(dato, null);
}
// --- Getters y Setters ---
public T getDato() { return dato; }
public void setDato(T dato) { this.dato = dato; }
public Nodo<T> getSiguiente() { return siguiente; }
public void setSiguiente(Nodo<T> sig) { this.siguiente = sig; }
@Override
public String toString() {
return "Nodo{" + dato + "}";
}
}
<T> para que el nodo pueda almacenar cualquier tipo de objeto (Integer, String, Empleado…). Esto es preferible a usar Object, porque el compilador puede verificar los tipos en tiempo de compilación y se evitan castings inseguros.
⚙️ Lista enlazada simple: implementación completa
A continuación se presenta una implementación profesional de una lista enlazada simple genérica con todas las operaciones fundamentales. La clase mantiene una referencia a la cabeza (primer nodo) y un contador de tamanio para obtener el tamaño en tiempo O(1):
/**
* Lista enlazada simple genérica.
* Operaciones: insertar, eliminar, buscar, recorrer, invertir.
*/
public class ListaEnlazada<T> {
private Nodo<T> cabeza; // Primer nodo de la lista
private int tamanio; // Número de elementos
public ListaEnlazada() {
this.cabeza = null;
this.tamanio = 0;
}
// ¿La lista está vacía?
public boolean esVacia() {
return cabeza == null;
}
// Número de elementos
public int tamanio() {
return tamanio;
}
// --- INSERCIÓN ---
/** Inserta al inicio de la lista — O(1) */
public void insertarInicio(T dato) {
cabeza = new Nodo<>(dato, cabeza);
tamanio++;
}
/** Inserta al final de la lista — O(n) */
public void insertarFinal(T dato) {
Nodo<T> nuevo = new Nodo<>(dato);
if (esVacia()) {
cabeza = nuevo;
} else {
Nodo<T> actual = cabeza;
while (actual.getSiguiente() != null) {
actual = actual.getSiguiente();
}
actual.setSiguiente(nuevo);
}
tamanio++;
}
/** Inserta en una posición específica (0-indexada) — O(n) */
public void insertarEn(int posicion, T dato) {
if (posicion < 0 || posicion > tamanio) {
throw new IndexOutOfBoundsException(
"Posición " + posicion + " fuera de rango [0, " + tamanio + "]");
}
if (posicion == 0) {
insertarInicio(dato);
return;
}
Nodo<T> actual = cabeza;
for (int i = 0; i < posicion - 1; i++) {
actual = actual.getSiguiente();
}
actual.setSiguiente(new Nodo<>(dato, actual.getSiguiente()));
tamanio++;
}
// --- ELIMINACIÓN ---
/** Elimina el primer nodo — O(1) */
public T eliminarInicio() {
if (esVacia()) {
throw new RuntimeException("Lista vacía");
}
T dato = cabeza.getDato();
cabeza = cabeza.getSiguiente();
tamanio--;
return dato;
}
/** Elimina el último nodo — O(n) */
public T eliminarFinal() {
if (esVacia()) {
throw new RuntimeException("Lista vacía");
}
if (tamanio == 1) {
return eliminarInicio();
}
Nodo<T> actual = cabeza;
while (actual.getSiguiente().getSiguiente() != null) {
actual = actual.getSiguiente();
}
T dato = actual.getSiguiente().getDato();
actual.setSiguiente(null);
tamanio--;
return dato;
}
// --- BÚSQUEDA Y ACCESO ---
/** Busca un dato y devuelve true si existe — O(n) */
public boolean contiene(T dato) {
Nodo<T> actual = cabeza;
while (actual != null) {
if (actual.getDato().equals(dato)) {
return true;
}
actual = actual.getSiguiente();
}
return false;
}
/** Obtiene el dato en una posición — O(n) */
public T obtener(int posicion) {
if (posicion < 0 || posicion >= tamanio) {
throw new IndexOutOfBoundsException(
"Posición " + posicion + " fuera de rango [0, " + (tamanio - 1) + "]");
}
Nodo<T> actual = cabeza;
for (int i = 0; i < posicion; i++) {
actual = actual.getSiguiente();
}
return actual.getDato();
}
// --- UTILIDADES ---
/** Invierte la lista in-place — O(n) */
public void invertir() {
Nodo<T> anterior = null;
Nodo<T> actual = cabeza;
while (actual != null) {
Nodo<T> siguiente = actual.getSiguiente();
actual.setSiguiente(anterior);
anterior = actual;
actual = siguiente;
}
cabeza = anterior;
}
/** Representación textual de la lista */
@Override
public String toString() {
StringBuilder sb = new StringBuilder("[");
Nodo<T> actual = cabeza;
while (actual != null) {
sb.append(actual.getDato());
if (actual.getSiguiente() != null) {
sb.append(" → ");
}
actual = actual.getSiguiente();
}
return sb.append("]").toString();
}
}
🛠️ Operaciones básicas
Veamos en acción cada una de las operaciones de la lista enlazada con un programa principal que demuestra su funcionamiento:
public class DemoListaEnlazada {
public static void main(String[] args) {
ListaEnlazada<String> colores = new ListaEnlazada<>();
// --- Inserción ---
colores.insertarInicio("verde"); // [verde]
colores.insertarInicio("rojo"); // [rojo → verde]
colores.insertarFinal("azul"); // [rojo → verde → azul]
colores.insertarEn(1, "amarillo"); // [rojo → amarillo → verde → azul]
System.out.println("Lista: " + colores);
System.out.println("Tamaño: " + colores.tamanio());
// --- Búsqueda ---
System.out.println("¿Contiene 'verde'? " + colores.contiene("verde"));
System.out.println("Elemento en pos. 2: " + colores.obtener(2));
// --- Eliminación ---
String primero = colores.eliminarInicio();
System.out.println("Eliminado inicio: " + primero);
String ultimo = colores.eliminarFinal();
System.out.println("Eliminado final: " + ultimo);
System.out.println("Lista: " + colores);
// --- Inversión ---
colores.insertarFinal("naranja");
colores.insertarFinal("morado");
System.out.println("Antes de invertir: " + colores);
colores.invertir();
System.out.println("Después de invertir: " + colores);
}
}
// Salida:
// Lista: [rojo → amarillo → verde → azul]
// Tamaño: 4
// ¿Contiene 'verde'? true
// Elemento en pos. 2: verde
// Eliminado inicio: rojo
// Eliminado final: azul
// Lista: [amarillo → verde]
// Antes de invertir: [amarillo → verde → naranja → morado]
// Después de invertir: [morado → naranja → verde → amarillo]
🔹 Complejidad temporal de las operaciones
| Operación | Lista simple | Con ref. al final | Lista doble |
|---|---|---|---|
| Insertar al inicio | O(1) | O(1) | O(1) |
| Insertar al final | O(n) | O(1) | O(1) |
| Eliminar al inicio | O(1) | O(1) | O(1) |
| Eliminar al final | O(n) | O(n) | O(1) |
| Buscar / Acceder por índice | O(n) | O(n) | O(n) |
| Insertar en posición conocida | O(1)* | O(1)* | O(1)* |
* O(1) si ya se dispone de la referencia al nodo anterior. Localizar esa posición cuesta O(n).
↔️ Lista doblemente enlazada
En una lista doblemente enlazada (doubly linked list), cada nodo contiene tres campos: el dato, una referencia al nodo siguiente y otra al nodo anterior. Esto permite recorrer la lista en ambas direcciones y eliminar un nodo en O(1) si se conoce su referencia, ya que no es necesario localizar el nodo previo.
/**
* Nodo para lista doblemente enlazada.
*/
public class NodoDoble<T> {
private T dato;
private NodoDoble<T> siguiente;
private NodoDoble<T> anterior;
public NodoDoble(T dato) {
this.dato = dato;
this.siguiente = null;
this.anterior = null;
}
// Getters y setters
public T getDato() { return dato; }
public NodoDoble<T> getSiguiente() { return siguiente; }
public void setSiguiente(NodoDoble<T> sig) { this.siguiente = sig; }
public NodoDoble<T> getAnterior() { return anterior; }
public void setAnterior(NodoDoble<T> ant) { this.anterior = ant; }
}
public class ListaDoble<T> {
private NodoDoble<T> cabeza;
private NodoDoble<T> cola; // Referencia al último nodo
private int tamanio;
public ListaDoble() {
cabeza = null;
cola = null;
tamanio = 0;
}
/** Inserta al inicio — O(1) */
public void insertarInicio(T dato) {
NodoDoble<T> nuevo = new NodoDoble<>(dato);
if (cabeza == null) {
cabeza = cola = nuevo;
} else {
nuevo.setSiguiente(cabeza);
cabeza.setAnterior(nuevo);
cabeza = nuevo;
}
tamanio++;
}
/** Inserta al final — O(1) gracias a la referencia 'cola' */
public void insertarFinal(T dato) {
NodoDoble<T> nuevo = new NodoDoble<>(dato);
if (cola == null) {
cabeza = cola = nuevo;
} else {
cola.setSiguiente(nuevo);
nuevo.setAnterior(cola);
cola = nuevo;
}
tamanio++;
}
/** Elimina un nodo específico — O(1) si se conoce la referencia */
public void eliminarNodo(NodoDoble<T> nodo) {
if (nodo == null) return;
if (nodo.getAnterior() != null) {
nodo.getAnterior().setSiguiente(nodo.getSiguiente());
} else {
cabeza = nodo.getSiguiente(); // era la cabeza
}
if (nodo.getSiguiente() != null) {
nodo.getSiguiente().setAnterior(nodo.getAnterior());
} else {
cola = nodo.getAnterior(); // era la cola
}
tamanio--;
}
/** Recorre de inicio a fin */
public String recorrerAdelante() {
StringBuilder sb = new StringBuilder("[");
NodoDoble<T> actual = cabeza;
while (actual != null) {
sb.append(actual.getDato());
if (actual.getSiguiente() != null) sb.append(" ↔ ");
actual = actual.getSiguiente();
}
return sb.append("]").toString();
}
/** Recorre de fin a inicio */
public String recorrerAtras() {
StringBuilder sb = new StringBuilder("[");
NodoDoble<T> actual = cola;
while (actual != null) {
sb.append(actual.getDato());
if (actual.getAnterior() != null) sb.append(" ↔ ");
actual = actual.getAnterior();
}
return sb.append("]").toString();
}
}
🔄 Lista circular
En una lista circular, el último nodo no apunta a null sino que apunta de vuelta al primer nodo, formando un ciclo cerrado. Esta variante resulta especialmente útil para modelar estructuras cíclicas como turnos rotativos, buffers circulares o planificadores de procesos round-robin.
/**
* Lista circular simple.
* El último nodo siempre apunta al primero.
*/
public class ListaCircular<T> {
private Nodo<T> ultimo; // Referencia al último nodo
private int tamanio;
public ListaCircular() {
ultimo = null;
tamanio = 0;
}
/** Inserta al inicio (después de último, antes del primero) — O(1) */
public void insertarInicio(T dato) {
Nodo<T> nuevo = new Nodo<>(dato);
if (ultimo == null) {
ultimo = nuevo;
nuevo.setSiguiente(nuevo); // Apunta a sí mismo
} else {
nuevo.setSiguiente(ultimo.getSiguiente()); // nuevo → primero
ultimo.setSiguiente(nuevo); // ultimo → nuevo
}
tamanio++;
}
/** Inserta al final — O(1) */
public void insertarFinal(T dato) {
insertarInicio(dato);
ultimo = ultimo.getSiguiente(); // El nuevo pasa a ser el último
}
/** Recorrido completo (una vuelta) */
@Override
public String toString() {
if (ultimo == null) return "[]";
StringBuilder sb = new StringBuilder("[");
Nodo<T> actual = ultimo.getSiguiente(); // Empieza en el primero
do {
sb.append(actual.getDato());
actual = actual.getSiguiente();
if (actual != ultimo.getSiguiente()) sb.append(" → ");
} while (actual != ultimo.getSiguiente());
return sb.append("] (circular)").toString();
}
}
while (actual != null) produciría un bucle infinito, ya que ningún nodo apunta a null. Siempre se debe usar una condición de parada basada en comparar con el nodo de inicio: do { ... } while (actual != inicio).
☕ LinkedList en la API de Java
Java proporciona la clase java.util.LinkedList<E> como parte del Java Collections Framework. Internamente es una lista doblemente enlazada que implementa las interfaces List, Deque y Queue, lo que la convierte en una estructura extremadamente versátil.
import java.util.LinkedList;
public class DemoLinkedListAPI {
public static void main(String[] args) {
LinkedList<String> ciudades = new LinkedList<>();
// --- Como List ---
ciudades.add("Madrid"); // Inserta al final
ciudades.add("Barcelona");
ciudades.addFirst("Sevilla"); // Inserta al inicio
ciudades.add(1, "Valencia"); // Inserta en posición 1
System.out.println("Lista: " + ciudades);
// [Sevilla, Valencia, Madrid, Barcelona]
// --- Como Deque (doble cola) ---
ciudades.offerFirst("Bilbao"); // Inserta al inicio
ciudades.offerLast("Málaga"); // Inserta al final
System.out.println("Primero: " + ciudades.peekFirst()); // Bilbao
System.out.println("Último: " + ciudades.peekLast()); // Málaga
// --- Eliminación ---
ciudades.removeFirst(); // Elimina Bilbao
ciudades.removeLast(); // Elimina Málaga
ciudades.remove("Madrid"); // Elimina Madrid por valor
// --- Recorrido con for-each ---
System.out.println("Ciudades restantes:");
for (String ciudad : ciudades) {
System.out.println(" • " + ciudad);
}
// --- Búsqueda ---
System.out.println("¿Contiene Valencia? " + ciudades.contains("Valencia"));
System.out.println("Índice de Barcelona: " + ciudades.indexOf("Barcelona"));
}
}
// Salida:
// Lista: [Sevilla, Valencia, Madrid, Barcelona]
// Primero: Bilbao
// Último: Málaga
// Ciudades restantes:
// • Sevilla
// • Valencia
// • Barcelona
// ¿Contiene Valencia? true
// Índice de Barcelona: 2
⚖️ ArrayList vs. LinkedList
Una de las preguntas más frecuentes en entrevistas técnicas y exámenes de programación es cuándo usar ArrayList y cuándo LinkedList. La respuesta depende del patrón de operaciones predominante:
| Operación | ArrayList | LinkedList | Ganador |
|---|---|---|---|
Acceso por índice (get(i)) | O(1) | O(n) | ArrayList |
Insertar al final (add(e)) | O(1) amortizado | O(1) | Empate |
| Insertar al inicio | O(n) | O(1) | LinkedList |
| Eliminar al inicio | O(n) | O(1) | LinkedList |
| Insertar en medio | O(n) | O(n)* | Depende |
| Uso de memoria | Compacto | Mayor (refs. extra) | ArrayList |
| Localidad de caché | Excelente | Pobre | ArrayList |
* LinkedList tiene O(1) para insertar si ya se dispone de un iterador en la posición, pero localizar esa posición cuesta O(n).
ArrayList es la mejor elección por defecto. Use LinkedList solo cuando las inserciones y eliminaciones frecuentes en los extremos sean la operación dominante (por ejemplo, implementando una cola o un deque), o cuando implemente pilas y colas manuales con propósitos académicos.
🏗️ Ejemplo integrador: gestión de tareas con prioridad
El siguiente ejemplo reúne todos los conceptos del artículo en un sistema práctico de gestión de tareas que utiliza una lista enlazada para mantener las tareas ordenadas por prioridad. Cada tarea se inserta en la posición correcta según su prioridad, manteniendo la lista siempre ordenada.
import java.util.LinkedList;
import java.util.Iterator;
/**
* Sistema de gestión de tareas con prioridad.
* Utiliza LinkedList de la API de Java para demostrar
* inserción ordenada, eliminación y recorrido.
*/
public class GestorTareas {
// Clase interna: representa una tarea
static class Tarea implements Comparable<Tarea> {
private String nombre;
private int prioridad; // 1 = máxima, 5 = mínima
private boolean completada;
public Tarea(String nombre, int prioridad) {
this.nombre = nombre;
this.prioridad = prioridad;
this.completada = false;
}
public void completar() { this.completada = true; }
@Override
public int compareTo(Tarea otra) {
return Integer.compare(this.prioridad, otra.prioridad);
}
@Override
public String toString() {
String estado = completada ? "✓" : "○";
return String.format("[%s] P%d: %s", estado, prioridad, nombre);
}
}
private LinkedList<Tarea> tareas = new LinkedList<>();
/** Inserta tarea manteniendo orden por prioridad */
public void agregarTarea(String nombre, int prioridad) {
Tarea nueva = new Tarea(nombre, prioridad);
int posicion = 0;
for (Tarea t : tareas) {
if (nueva.compareTo(t) < 0) break;
posicion++;
}
tareas.add(posicion, nueva);
System.out.println("+ Añadida: " + nueva);
}
/** Completa la tarea de mayor prioridad (primera) */
public void completarSiguiente() {
if (tareas.isEmpty()) {
System.out.println("No hay tareas pendientes.");
return;
}
Tarea t = tareas.getFirst();
t.completar();
System.out.println("Completada: " + t);
tareas.removeFirst();
}
/** Elimina todas las tareas completadas */
public void limpiarCompletadas() {
Iterator<Tarea> it = tareas.iterator();
int eliminadas = 0;
while (it.hasNext()) {
if (it.next().completada) {
it.remove();
eliminadas++;
}
}
System.out.println("Limpiadas " + eliminadas + " tareas completadas.");
}
/** Muestra el estado actual */
public void mostrarTareas() {
System.out.println("\n=== Tareas pendientes (" + tareas.size() + ") ===");
if (tareas.isEmpty()) {
System.out.println(" (ninguna)");
} else {
for (Tarea t : tareas) {
System.out.println(" " + t);
}
}
System.out.println();
}
// --- Programa principal ---
public static void main(String[] args) {
GestorTareas gestor = new GestorTareas();
// Inserción ordenada por prioridad
gestor.agregarTarea("Corregir bug crítico en producción", 1);
gestor.agregarTarea("Escribir documentación del API", 3);
gestor.agregarTarea("Actualizar dependencias Maven", 4);
gestor.agregarTarea("Revisar pull request del equipo", 2);
gestor.agregarTarea("Preparar demo para el cliente", 1);
gestor.mostrarTareas();
// Completar las dos tareas más urgentes
gestor.completarSiguiente();
gestor.completarSiguiente();
gestor.mostrarTareas();
}
}
// Salida:
// + Añadida: [○] P1: Corregir bug crítico en producción
// + Añadida: [○] P3: Escribir documentación del API
// + Añadida: [○] P4: Actualizar dependencias Maven
// + Añadida: [○] P2: Revisar pull request del equipo
// + Añadida: [○] P1: Preparar demo para el cliente
//
// === Tareas pendientes (5) ===
// [○] P1: Corregir bug crítico en producción
// [○] P1: Preparar demo para el cliente
// [○] P2: Revisar pull request del equipo
// [○] P3: Escribir documentación del API
// [○] P4: Actualizar dependencias Maven
//
// Completada: [✓] P1: Corregir bug crítico en producción
// Completada: [✓] P1: Preparar demo para el cliente
//
// === Tareas pendientes (3) ===
// [○] P2: Revisar pull request del equipo
// [○] P3: Escribir documentación del API
// [○] P4: Actualizar dependencias Maven
✏️ Ejercicios resueltos
📝 Ejercicio 1 — Contar nodos de una lista enlazada
Implementa un método contar() que recorra una lista enlazada simple y devuelva el número de nodos, sin usar la variable tamanio. El método debe funcionar recorriendo los nodos secuencialmente.
Ver solución
/** Cuenta los nodos recorriendo la lista — O(n) */
public int contar() {
int contador = 0;
Nodo<T> actual = cabeza;
while (actual != null) {
contador++;
actual = actual.getSiguiente();
}
return contador;
}
// Versión recursiva
public int contarRecursivo(Nodo<T> nodo) {
if (nodo == null) return 0;
return 1 + contarRecursivo(nodo.getSiguiente());
}
📝 Ejercicio 2 — Detectar si una lista tiene un ciclo
Implementa un método que determine si una lista enlazada contiene un ciclo (es decir, algún nodo apunta a un nodo anterior). Utiliza el algoritmo de Floyd (tortuga y liebre) con dos punteros que avanzan a distinta velocidad.
Ver solución
/**
* Detecta ciclos usando el algoritmo de Floyd.
* - Tortuga avanza 1 nodo por paso
* - Liebre avanza 2 nodos por paso
* - Si se encuentran, hay ciclo
* Complejidad: O(n) tiempo, O(1) espacio
*/
public boolean tieneCiclo() {
if (cabeza == null) return false;
Nodo<T> tortuga = cabeza;
Nodo<T> liebre = cabeza;
while (liebre != null && liebre.getSiguiente() != null) {
tortuga = tortuga.getSiguiente(); // 1 paso
liebre = liebre.getSiguiente().getSiguiente(); // 2 pasos
if (tortuga == liebre) {
return true; // Se encontraron → hay ciclo
}
}
return false; // Liebre llegó a null → no hay ciclo
}
El algoritmo funciona porque si hay un ciclo, la liebre (más rápida) acabará «dando la vuelta» y alcanzando a la tortuga dentro del ciclo. Si no hay ciclo, la liebre llegará a null y el bucle terminará.
📝 Ejercicio 3 — Encontrar el n-ésimo nodo desde el final
Dado un número n, encuentra el n-ésimo nodo contando desde el final de la lista en una sola pasada (sin conocer previamente el tamaño). Pista: utiliza la técnica de dos punteros con separación fija.
Ver solución
/**
* Devuelve el n-ésimo nodo desde el final (1-indexado).
* Técnica: dos punteros separados por n posiciones.
* Cuando el adelantado llega a null, el atrasado
* está en la posición deseada.
*/
public T obtenerDesdeElFinal(int n) {
if (n <= 0) throw new IllegalArgumentException("n debe ser > 0");
Nodo<T> adelantado = cabeza;
Nodo<T> atrasado = cabeza;
// Adelantar el primer puntero n posiciones
for (int i = 0; i < n; i++) {
if (adelantado == null) {
throw new IndexOutOfBoundsException(
"La lista tiene menos de " + n + " elementos");
}
adelantado = adelantado.getSiguiente();
}
// Avanzar ambos hasta que el adelantado llegue al final
while (adelantado != null) {
adelantado = adelantado.getSiguiente();
atrasado = atrasado.getSiguiente();
}
return atrasado.getDato();
}
// Ejemplo de uso:
// Lista: [10 → 20 → 30 → 40 → 50]
// obtenerDesdeElFinal(1) → 50 (último)
// obtenerDesdeElFinal(3) → 30 (tercero desde el final)
❓ Preguntas frecuentes sobre Listas enlazadas en Java: implementación paso a paso
Las dudas más comunes respondidas de forma clara y directa.
💬 Foro de discusión
¿Tienes dudas sobre Listas enlazadas en Java: implementación paso a paso? Comparte tu pregunta con la comunidad.
Todavía no hay mensajes. ¡Sé el primero en participar!