Divide y vencerás en Java: algoritmos recursivos con ejemplos

📅 Actualizado en febrero 2026 ✍️ Ángel López ⏱️ 20 min de lectura ✓ Nivel intermedio ★ ★ ★ ★ ★ (5/5)

⚔️ ¿Qué es divide y vencerás?

Divide y vencerás (divide and conquer) es uno de los paradigmas algorítmicos más importantes y elegantes de la informática. Su idea central es engañosamente simple: si un problema es demasiado grande para resolverlo directamente, divídelo en subproblemas más pequeños, resuélvelos por separado y combina sus soluciones para obtener la solución del problema original.

Esta estrategia tiene raíces milenarias — Julio César la empleó en la política y Napoleón en la guerra — pero en informática adquirió forma rigurosa con los trabajos de John von Neumann (MergeSort, 1945) y Tony Hoare (QuickSort, 1961). Hoy es la base de algunos de los algoritmos más eficientes que existen: ordenación, búsqueda, multiplicación de matrices, transformada rápida de Fourier y criptografía, entre otros.

📘 Definición formal: Divide y vencerás es una técnica de diseño de algoritmos que resuelve un problema descomponiéndolo recursivamente en dos o más subproblemas del mismo tipo, resolviendo los subproblemas cuando son lo suficientemente pequeños (caso base) y combinando sus soluciones para producir la solución del problema original.

En Java, esta técnica se implementa naturalmente mediante recursión y, desde Java 7, también puede paralelizarse con el framework Fork/Join para aprovechar procesadores multinúcleo. Comprender divide y vencerás es esencial tanto para entrevistas técnicas como para desarrollar software eficiente en el mundo real.

🔄 Las tres fases: dividir, conquistar, combinar

Todo algoritmo de divide y vencerás sigue exactamente tres fases. Entenderlas con claridad es la clave para diseñar tus propios algoritmos con esta técnica:

Fase Descripción Ejemplo (MergeSort)
1. Dividir Descomponer el problema en subproblemas más pequeños del mismo tipo. Idealmente, los subproblemas deben tener tamaño similar para obtener la mejor eficiencia. Partir el array por la mitad: [5,3,8,1][5,3] y [8,1]
2. Conquistar Resolver cada subproblema. Si es lo suficientemente pequeño (caso base), resolverlo directamente. Si no, aplicar divide y vencerás recursivamente. Seguir dividiendo: [5,3][5] y [3] (caso base: 1 elemento, ya está ordenado)
3. Combinar Fusionar las soluciones de los subproblemas para construir la solución del problema original. Esta suele ser la fase más compleja de implementar. Mezclar ordenadamente: [3,5] + [1,8][1,3,5,8]

La elegancia de esta técnica radica en que la misma función se aplica recursivamente hasta llegar a casos triviales. No necesitas saber cómo resolver el problema grande: solo necesitas saber cómo dividirlo y cómo combinar soluciones parciales.

Visualización del proceso (MergeSort)
         [5, 3, 8, 1, 4, 7, 2, 6]          ← Problema original
                /              \
       [5, 3, 8, 1]      [4, 7, 2, 6]       ← DIVIDIR
        /        \          /        \
    [5, 3]    [8, 1]    [4, 7]    [2, 6]     ← DIVIDIR
    /   \     /   \     /   \     /   \
  [5]  [3]  [8]  [1]  [4]  [7]  [2]  [6]    ← CASO BASE

  [3,5]    [1,8]    [4,7]    [2,6]           ← COMBINAR (mezclar)
     [1, 3, 5, 8]      [2, 4, 6, 7]         ← COMBINAR
         [1, 2, 3, 4, 5, 6, 7, 8]           ← SOLUCIÓN

📋 Plantilla general en Java

La estructura de cualquier algoritmo divide y vencerás en Java sigue un patrón reconocible. Esta plantilla te servirá como punto de partida para diseñar tus propios algoritmos:

Plantilla genérica — Divide y vencerás
/**
 * Plantilla genérica de divide y vencerás.
 * @param problema  el problema (o porción) a resolver
 * @return          la solución al problema
 */
public Solucion divideYVenceras(Problema problema) {

    // 1. CASO BASE: si el problema es lo bastante pequeño, resolverlo directamente
    if (esCasoBase(problema)) {
        return resolverDirectamente(problema);
    }

    // 2. DIVIDIR: descomponer en subproblemas
    Problema[] subproblemas = dividir(problema);

    // 3. CONQUISTAR: resolver cada subproblema recursivamente
    Solucion[] soluciones = new Solucion[subproblemas.length];
    for (int i = 0; i < subproblemas.length; i++) {
        soluciones[i] = divideYVenceras(subproblemas[i]);
    }

    // 4. COMBINAR: fusionar las soluciones parciales
    return combinar(soluciones);
}
✅ Regla de oro: La eficiencia de un algoritmo divide y vencerás depende de tres factores: cuántos subproblemas se generan (a), cuánto se reduce el tamaño en cada división (b) y cuánto trabajo cuesta dividir y combinar (f(n)). Estos tres factores determinan la complejidad final según el Teorema Maestro.

🔍 Ejemplo 1: búsqueda binaria

La búsqueda binaria es el ejemplo más sencillo de divide y vencerás. Dado un array ordenado, busca un elemento descartando la mitad del array en cada paso. Es un caso especial donde solo se resuelve un subproblema de los dos posibles (a veces llamado decrease and conquer).

BusquedaBinaria.java
public class BusquedaBinaria {

    /**
     * Busca un valor en un array ordenado usando divide y vencerás.
     * @param arr   array ordenado de enteros
     * @param valor el valor a buscar
     * @param izq   índice izquierdo del rango actual
     * @param der   índice derecho del rango actual
     * @return      índice del valor, o -1 si no se encuentra
     */
    public static int buscar(int[] arr, int valor, int izq, int der) {

        // CASO BASE: rango vacío → no encontrado
        if (izq > der) {
            return -1;
        }

        // DIVIDIR: calcular el punto medio
        int medio = izq + (der - izq) / 2;  // Evita overflow con (izq+der)/2

        // CONQUISTAR: comparar y elegir la mitad correcta
        if (arr[medio] == valor) {
            return medio;                           // ¡Encontrado!
        } else if (valor < arr[medio]) {
            return buscar(arr, valor, izq, medio - 1);  // Buscar en mitad izquierda
        } else {
            return buscar(arr, valor, medio + 1, der);  // Buscar en mitad derecha
        }
        // COMBINAR: no es necesario (solo hay un subproblema)
    }

    public static void main(String[] args) {
        int[] datos = {2, 5, 8, 12, 16, 23, 38, 56, 72, 91};
        int objetivo = 23;

        int indice = buscar(datos, objetivo, 0, datos.length - 1);

        if (indice >= 0) {
            System.out.println("Valor " + objetivo + " encontrado en índice " + indice);
        } else {
            System.out.println("Valor " + objetivo + " no encontrado");
        }
    }
}

📋 Salida esperada

Salida en consola
Valor 23 encontrado en índice 5
⚠️ Detalle importante: Usamos izq + (der - izq) / 2 en lugar de (izq + der) / 2 para evitar un desbordamiento de enteros (integer overflow) cuando izq y der son valores grandes. Este bug estuvo presente durante años en la implementación de la JDK de Java y fue corregido por Joshua Bloch en 2006.

Complejidad: T(n) = T(n/2) + O(1) → O(log n). Cada paso descarta la mitad del array, así que para 1.000.000 de elementos solo necesita ~20 comparaciones.

📊 Ejemplo 2: MergeSort (ordenación por mezcla)

MergeSort, inventado por John von Neumann en 1945, es el ejemplo canónico de divide y vencerás aplicado a la ordenación. Divide el array por la mitad, ordena recursivamente cada mitad y luego mezcla (merge) las dos mitades ordenadas en un único array ordenado.

MergeSort.java — Implementación completa
public class MergeSort {

    /**
     * Ordena un array de enteros usando MergeSort.
     * @param arr array a ordenar
     * @param izq índice izquierdo (inclusivo)
     * @param der índice derecho (inclusivo)
     */
    public static void mergeSort(int[] arr, int izq, int der) {

        // CASO BASE: un solo elemento o rango vacío → ya está ordenado
        if (izq >= der) {
            return;
        }

        // DIVIDIR: calcular el punto medio
        int medio = izq + (der - izq) / 2;

        // CONQUISTAR: ordenar recursivamente cada mitad
        mergeSort(arr, izq, medio);       // Mitad izquierda
        mergeSort(arr, medio + 1, der);   // Mitad derecha

        // COMBINAR: mezclar las dos mitades ordenadas
        merge(arr, izq, medio, der);
    }

    /**
     * Mezcla dos subarrays ordenados: arr[izq..medio] y arr[medio+1..der].
     */
    private static void merge(int[] arr, int izq, int medio, int der) {

        // Crear copias temporales de las dos mitades
        int n1 = medio - izq + 1;
        int n2 = der - medio;
        int[] izqArr = new int[n1];
        int[] derArr = new int[n2];

        System.arraycopy(arr, izq, izqArr, 0, n1);
        System.arraycopy(arr, medio + 1, derArr, 0, n2);

        // Mezclar comparando elemento a elemento
        int i = 0, j = 0, k = izq;
        while (i < n1 && j < n2) {
            if (izqArr[i] <= derArr[j]) {
                arr[k++] = izqArr[i++];
            } else {
                arr[k++] = derArr[j++];
            }
        }

        // Copiar elementos restantes (si los hay)
        while (i < n1) arr[k++] = izqArr[i++];
        while (j < n2) arr[k++] = derArr[j++];
    }

    public static void main(String[] args) {
        int[] datos = {38, 27, 43, 3, 9, 82, 10};
        System.out.println("Original:  " + java.util.Arrays.toString(datos));

        mergeSort(datos, 0, datos.length - 1);

        System.out.println("Ordenado:  " + java.util.Arrays.toString(datos));
    }
}
Salida en consola
Original:  [38, 27, 43, 3, 9, 82, 10]
Ordenado:  [3, 9, 10, 27, 38, 43, 82]

Complejidad: T(n) = 2T(n/2) + O(n) → O(n log n) en todos los casos (mejor, promedio y peor). Usa O(n) de memoria extra por los arrays temporales de la mezcla. Es un algoritmo estable (preserva el orden relativo de elementos iguales).

📘 Dato curioso: Java usa TimSort (una variante optimizada de MergeSort) en Arrays.sort() para objetos y Collections.sort(). TimSort combina MergeSort con InsertionSort para subarrays pequeños, logrando O(n) en arrays parcialmente ordenados.

⚡ Ejemplo 3: QuickSort (ordenación rápida)

QuickSort, diseñado por Tony Hoare en 1961, es otro algoritmo de ordenación divide y vencerás, pero con un enfoque diferente a MergeSort. En lugar de dividir por posición y mezclar después, QuickSort elige un pivote, particiona el array colocando los menores a la izquierda y los mayores a la derecha, y luego ordena recursivamente cada partición.

QuickSort.java — Implementación con partición de Lomuto
public class QuickSort {

    /**
     * Ordena un array de enteros usando QuickSort.
     */
    public static void quickSort(int[] arr, int izq, int der) {

        // CASO BASE: rango de 0 o 1 elementos
        if (izq >= der) {
            return;
        }

        // DIVIDIR + COMBINAR: particionar alrededor del pivote
        int indicePivote = particionar(arr, izq, der);

        // CONQUISTAR: ordenar recursivamente cada partición
        quickSort(arr, izq, indicePivote - 1);   // Menores que pivote
        quickSort(arr, indicePivote + 1, der);    // Mayores que pivote
    }

    /**
     * Partición de Lomuto: usa el último elemento como pivote.
     * Coloca todos los menores a la izquierda del pivote
     * y todos los mayores a la derecha.
     * @return el índice final del pivote
     */
    private static int particionar(int[] arr, int izq, int der) {
        int pivote = arr[der];  // Elegir último elemento como pivote
        int i = izq - 1;       // Frontera de los elementos menores

        for (int j = izq; j < der; j++) {
            if (arr[j] <= pivote) {
                i++;
                intercambiar(arr, i, j);
            }
        }

        // Colocar el pivote en su posición final
        intercambiar(arr, i + 1, der);
        return i + 1;
    }

    private static void intercambiar(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

    public static void main(String[] args) {
        int[] datos = {10, 80, 30, 90, 40, 50, 70};
        System.out.println("Original:  " + java.util.Arrays.toString(datos));

        quickSort(datos, 0, datos.length - 1);

        System.out.println("Ordenado:  " + java.util.Arrays.toString(datos));
    }
}
Salida en consola
Original:  [10, 80, 30, 90, 40, 50, 70]
Ordenado:  [10, 30, 40, 50, 70, 80, 90]

📊 MergeSort vs. QuickSort: comparativa

Característica MergeSort QuickSort
Mejor caso O(n log n) O(n log n)
Caso promedio O(n log n) O(n log n)
Peor caso O(n log n) O(n²) — array ya ordenado con pivote extremo
Memoria extra O(n) O(log n) — solo la pila de recursión
Estable No (en la versión estándar)
In-place No
Velocidad práctica Buena Excelente (mejor localidad de caché)
Uso en Java TimSort para objetos (Arrays.sort(Object[])) Dual-pivot para primitivos (Arrays.sort(int[]))

📐 Análisis de complejidad y el Teorema Maestro

Los algoritmos divide y vencerás generan una relación de recurrencia que describe su complejidad temporal. La forma general es:

Recurrencia general
T(n) = a · T(n/b) + f(n)

Donde:
  a = número de subproblemas generados en cada división
  b = factor por el que se reduce el tamaño del problema
  f(n) = coste de dividir + combinar

El Teorema Maestro permite resolver estas recurrencias directamente. Para T(n) = aT(n/b) + O(nd), compara logb(a) con d:

Caso Condición Complejidad Ejemplo
Caso 1 logb(a) > d O(nlogb(a)) Karatsuba: T(n) = 3T(n/2) + O(n) → O(n1.585)
Caso 2 logb(a) = d O(nd · log n) MergeSort: T(n) = 2T(n/2) + O(n) → O(n log n)
Caso 3 logb(a) < d O(nd) Búsqueda binaria: T(n) = T(n/2) + O(1) → O(log n)
✅ Ejemplo práctico: Para MergeSort, a=2 (dos subproblemas), b=2 (tamaño mitad) y d=1 (la mezcla cuesta O(n)). Como log₂(2) = 1 = d, estamos en el Caso 2, lo que da O(n log n).

🚀 Divide y vencerás en Java moderno: Fork/Join

Desde Java 7, el framework java.util.concurrent.ForkJoinPool permite implementar algoritmos divide y vencerás que se ejecutan en paralelo, aprovechando los múltiples núcleos del procesador. La idea es dividir el trabajo en tareas, lanzarlas (fork) en hilos independientes y esperar sus resultados (join).

MergeSortParalelo.java — Usando Fork/Join
import java.util.concurrent.RecursiveAction;
import java.util.concurrent.ForkJoinPool;

public class MergeSortParalelo extends RecursiveAction {

    private static final int UMBRAL = 1024; // Debajo de este tamaño, usar sort secuencial
    private final int[] arr;
    private final int izq, der;

    public MergeSortParalelo(int[] arr, int izq, int der) {
        this.arr = arr;
        this.izq = izq;
        this.der = der;
    }

    @Override
    protected void compute() {
        // Caso base: si es pequeño, ordenar secuencialmente
        if (der - izq < UMBRAL) {
            java.util.Arrays.sort(arr, izq, der + 1);
            return;
        }

        // Dividir
        int medio = izq + (der - izq) / 2;

        // Crear subtareas
        MergeSortParalelo tareaIzq = new MergeSortParalelo(arr, izq, medio);
        MergeSortParalelo tareaDer = new MergeSortParalelo(arr, medio + 1, der);

        // Fork: lanzar la tarea izquierda en otro hilo
        tareaIzq.fork();

        // Ejecutar la tarea derecha en el hilo actual
        tareaDer.compute();

        // Join: esperar a que termine la tarea izquierda
        tareaIzq.join();

        // Combinar: mezclar las dos mitades ordenadas
        merge(arr, izq, medio, der);
    }

    private void merge(int[] arr, int izq, int medio, int der) {
        int[] temp = new int[der - izq + 1];
        int i = izq, j = medio + 1, k = 0;
        while (i <= medio && j <= der) {
            temp[k++] = (arr[i] <= arr[j]) ? arr[i++] : arr[j++];
        }
        while (i <= medio) temp[k++] = arr[i++];
        while (j <= der)   temp[k++] = arr[j++];
        System.arraycopy(temp, 0, arr, izq, temp.length);
    }

    public static void main(String[] args) {
        int[] datos = new int[1_000_000];
        java.util.Random rng = new java.util.Random(42);
        for (int i = 0; i < datos.length; i++) datos[i] = rng.nextInt();

        long inicio = System.nanoTime();
        ForkJoinPool pool = ForkJoinPool.commonPool();
        pool.invoke(new MergeSortParalelo(datos, 0, datos.length - 1));
        long fin = System.nanoTime();

        System.out.println("Ordenados " + datos.length + " elementos en "
                         + (fin - inicio) / 1_000_000 + " ms");
        System.out.println("¿Ordenado correctamente? "
                         + estaOrdenado(datos));
    }

    private static boolean estaOrdenado(int[] arr) {
        for (int i = 1; i < arr.length; i++) {
            if (arr[i] < arr[i - 1]) return false;
        }
        return true;
    }
}
📘 Clave del rendimiento: El umbral (UMBRAL) es crítico. Si es demasiado bajo, se crean demasiadas tareas y el overhead de gestión supera la ganancia del paralelismo. Si es demasiado alto, no se aprovechan los núcleos. Valores entre 1.000 y 10.000 suelen funcionar bien en la práctica.

🧮 Otros algoritmos clásicos de divide y vencerás

Más allá de la ordenación y la búsqueda, divide y vencerás es la base de algoritmos que han transformado la informática moderna:

Algoritmo Problema que resuelve Complejidad Importancia
Strassen Multiplicación de matrices O(n2.807) vs. O(n³) naíf Primer algoritmo subcúbico; base de variantes aún más rápidas.
Karatsuba Multiplicación de enteros grandes O(n1.585) vs. O(n²) escolar Fundamental en criptografía (RSA, números de miles de dígitos).
FFT (Cooley-Tukey) Transformada rápida de Fourier O(n log n) vs. O(n²) Compresión de audio/vídeo (MP3, JPEG), telecomunicaciones, IA.
Par más cercano Encontrar los dos puntos más cercanos en un plano O(n log n) vs. O(n²) fuerza bruta Sistemas GIS, gráficos, robótica.
Exponenciación rápida Calcular an eficientemente O(log n) vs. O(n) Criptografía, aritmética modular.
MapReduce Procesamiento masivo de datos Escalado horizontal Google, Hadoop, Spark. Divide y vencerás a escala de clúster.

🎯 Cuándo usar y cuándo evitar esta técnica

✅ Usa divide y vencerás cuando:

El problema puede descomponerse en subproblemas independientes del mismo tipo. Esta es la condición fundamental. Si los subproblemas se solapan (comparten cálculos), es mejor programación dinámica.

La combinación de resultados es eficiente. Si combinar las soluciones parciales cuesta tanto como resolver el problema original, divide y vencerás no aporta ventaja.

Puedes aprovechar el paralelismo. Los subproblemas independientes son naturalmente paralelizables con Fork/Join, streams paralelos o MapReduce.

❌ Evita divide y vencerás cuando:

Los subproblemas se solapan. Si calculas los mismos subproblemas repetidamente (como en la secuencia de Fibonacci), necesitas memoización → usa programación dinámica.

El overhead de la recursión es excesivo. Para problemas pequeños, la recursión añade coste de llamadas y pila. Es habitual establecer un umbral e usar un algoritmo simple (InsertionSort para arrays pequeños en MergeSort/QuickSort).

No existe una forma natural de combinar subsoluciones. Si fusionar las respuestas parciales es tan costoso como resolver el problema completo, la técnica no ofrece ventaja.

🐛 Errores comunes al implementar divide y vencerás

Error Consecuencia Solución
Caso base incorrecto Recursión infinita → StackOverflowError Verificar que toda cadena de divisiones termina en el caso base. Probar con arrays de 0, 1 y 2 elementos.
Overflow en cálculo del medio Índice negativo → ArrayIndexOutOfBoundsException Usar izq + (der - izq) / 2 en vez de (izq + der) / 2.
Subproblemas desbalanceados Complejidad degenerada (ej: QuickSort O(n²) con pivote extremo) Elegir pivote aleatorio o mediana de tres. Dividir lo más equitativamente posible.
No reducir el tamaño Recursión infinita Garantizar que cada subproblema es estrictamente más pequeño que el original. Cuidado con los límites izq/der.
Ignorar el paso de combinación Resultado incorrecto Verificar que la fase de combinación reconstruye correctamente la solución global a partir de las parciales.

📝 Ejercicios resueltos

🏋️ Ejercicio 1: Máximo de un array con divide y vencerás

Enunciado: Implementa una función que encuentre el elemento máximo de un array de enteros usando divide y vencerás. No uses bucles en la función recursiva.

Solución: Máximo con DyV
public class MaximoDyV {

    public static int maximo(int[] arr, int izq, int der) {
        // Caso base: un solo elemento
        if (izq == der) {
            return arr[izq];
        }

        // Caso base: dos elementos
        if (der - izq == 1) {
            return Math.max(arr[izq], arr[der]);
        }

        // Dividir
        int medio = izq + (der - izq) / 2;

        // Conquistar
        int maxIzq = maximo(arr, izq, medio);
        int maxDer = maximo(arr, medio + 1, der);

        // Combinar
        return Math.max(maxIzq, maxDer);
    }

    public static void main(String[] args) {
        int[] datos = {3, 7, 2, 9, 1, 8, 5, 4};
        int max = maximo(datos, 0, datos.length - 1);
        System.out.println("Máximo: " + max);  // Máximo: 9
    }
}

// Complejidad: T(n) = 2T(n/2) + O(1) → O(n)
// (No mejora la versión lineal, pero ilustra la técnica)

🏋️ Ejercicio 2: Exponenciación rápida

Enunciado: Implementa una función que calcule baseexponente usando divide y vencerás, aprovechando que an = (an/2)² si n es par, y an = a · (an/2)² si n es impar. La complejidad debe ser O(log n).

Solución: Exponenciación rápida
public class ExponenciacionRapida {

    /**
     * Calcula base^exponente usando divide y vencerás.
     * Complejidad: O(log n) multiplicaciones.
     */
    public static long potencia(long base, int exponente) {
        // Caso base
        if (exponente == 0) return 1;
        if (exponente == 1) return base;

        // Dividir: resolver el subproblema de tamaño mitad
        long mitad = potencia(base, exponente / 2);

        // Combinar
        if (exponente % 2 == 0) {
            return mitad * mitad;               // a^n = (a^(n/2))²
        } else {
            return base * mitad * mitad;        // a^n = a · (a^(n/2))²
        }
    }

    public static void main(String[] args) {
        System.out.println("2^10  = " + potencia(2, 10));   // 1024
        System.out.println("3^13  = " + potencia(3, 13));   // 1594323
        System.out.println("5^0   = " + potencia(5, 0));    // 1

        // Comparar: la versión ingenua haría 13 multiplicaciones para 3^13
        // La versión DyV solo necesita ~4 (log₂(13) ≈ 3.7)
    }
}

🏋️ Ejercicio 3: Contar inversiones en un array

Enunciado: Una inversión es un par (i, j) donde i < j pero arr[i] > arr[j]. Implementa una función que cuente el número total de inversiones en un array usando una modificación de MergeSort. La complejidad debe ser O(n log n), no O(n²).

Solución: Contar inversiones con MergeSort modificado
public class ContarInversiones {

    /**
     * Cuenta inversiones usando MergeSort modificado.
     * Cada vez que en merge() un elemento de la derecha es menor
     * que uno de la izquierda, contamos las inversiones.
     */
    public static long contarInversiones(int[] arr, int izq, int der) {
        if (izq >= der) return 0;

        int medio = izq + (der - izq) / 2;

        // Contar inversiones en cada mitad + las cruzadas en merge
        long inversiones = 0;
        inversiones += contarInversiones(arr, izq, medio);
        inversiones += contarInversiones(arr, medio + 1, der);
        inversiones += mergeYContar(arr, izq, medio, der);

        return inversiones;
    }

    private static long mergeYContar(int[] arr, int izq, int medio, int der) {
        int n1 = medio - izq + 1;
        int n2 = der - medio;
        int[] izqArr = new int[n1];
        int[] derArr = new int[n2];
        System.arraycopy(arr, izq, izqArr, 0, n1);
        System.arraycopy(arr, medio + 1, derArr, 0, n2);

        int i = 0, j = 0, k = izq;
        long inversiones = 0;

        while (i < n1 && j < n2) {
            if (izqArr[i] <= derArr[j]) {
                arr[k++] = izqArr[i++];
            } else {
                arr[k++] = derArr[j++];
                // Todos los elementos restantes en izqArr son mayores
                // que derArr[j], así que todos forman inversiones
                inversiones += (n1 - i);
            }
        }

        while (i < n1) arr[k++] = izqArr[i++];
        while (j < n2) arr[k++] = derArr[j++];

        return inversiones;
    }

    public static void main(String[] args) {
        int[] datos = {8, 4, 2, 1};
        int[] copia = datos.clone();

        long inv = contarInversiones(copia, 0, copia.length - 1);
        System.out.println("Array: " + java.util.Arrays.toString(datos));
        System.out.println("Inversiones: " + inv);  // 6 inversiones
        // (8,4)(8,2)(8,1)(4,2)(4,1)(2,1)
    }
}

❓ Preguntas frecuentes sobre Divide y vencerás en Java: algoritmos recursivos con ejemplos

Las dudas más comunes respondidas de forma clara y directa.

Ambas dividen un problema en subproblemas más pequeños. La diferencia clave es que divide y vencerás resuelve subproblemas independientes (sin solapamiento), mientras que programación dinámica resuelve subproblemas que se repiten y almacena sus resultados para no recalcularlos (memoización). Por ejemplo, MergeSort usa divide y vencerás porque las mitades son independientes, pero Fibonacci se beneficia de programación dinámica porque los subproblemas se solapan.
Depende del contexto. MergeSort garantiza O(n log n) en todos los casos y es estable, pero requiere O(n) de memoria extra. QuickSort es O(n log n) en promedio, usa O(log n) de memoria y suele ser más rápido en la práctica por mejor localidad de caché, pero su peor caso es O(n²). Java usa TimSort (basado en MergeSort) para objetos en Arrays.sort() y una variante dual-pivot de QuickSort para primitivos.
El Teorema Maestro es una fórmula que permite calcular la complejidad de algoritmos recursivos de tipo divide y vencerás sin resolver la recurrencia manualmente. Se aplica a recurrencias de la forma T(n) = aT(n/b) + O(n^d), donde a es el número de subproblemas, b el factor de reducción y d el exponente del trabajo de dividir/combinar. Comparando log_b(a) con d se obtiene directamente la complejidad final.
El framework Fork/Join, introducido en Java 7, permite implementar algoritmos divide y vencerás en paralelo aprovechando múltiples núcleos del procesador. Usa un ForkJoinPool con work-stealing: cada hilo tiene su cola de tareas, y cuando uno termina, roba trabajo de otro. Se implementa extendiendo RecursiveTask (con resultado) o RecursiveAction (sin resultado), definiendo un umbral a partir del cual se deja de dividir y se resuelve secuencialmente.
No. Divide y vencerás es un tipo específico de recursión donde el problema se descompone en subproblemas independientes del mismo tipo, se resuelven recursivamente y sus soluciones se combinan. Existen otros paradigmas recursivos como decrease and conquer (reducir el problema en un solo subproblema más pequeño, por ejemplo búsqueda lineal recursiva) o backtracking (explorar y retroceder). Divide y vencerás siempre genera múltiples subproblemas, nunca uno solo.
El caso base debe cumplir dos criterios: ser lo suficientemente pequeño para resolverse directamente sin recursión, y garantizar que toda cadena de divisiones termine en él. Típicamente el caso base es un array de 0 o 1 elementos, un rango vacío, o un problema de tamaño trivial. Un caso base incorrecto (demasiado grande o demasiado pequeño) puede causar resultados erróneos o desbordamiento de pila por recursión infinita.
Valora este artículo

💬 Foro de discusión

¿Tienes dudas sobre Divide y vencerás en Java: algoritmos recursivos con ejemplos? Comparte tu pregunta con la comunidad.

¿Tienes cuenta? o comenta como invitado ↓

Todavía no hay mensajes. ¡Sé el primero en participar!