¿Cuál es el algoritmo más optimizado para encontrar la suma de la diferencia absoluta de cada par distinto en una matriz entera?

Intuición

Primero abordé este problema escribiendo una matriz de ejemplo, digamos: 3, -2, -5, 18. Llamaré a estos elementos A, B, C, D, respectivamente.

Luego, manualmente pasé por el proceso de encontrar la suma de las diferencias absolutas para todos los pares para A (por ejemplo, (A, B), (A, C), (A, D)). Esa suma es:

| BA | + | CA | + | DA |

Mi deseo era que pudiera ignorar los signos de valor absoluto y reescribir la expresión como:

(BA) + (CA) + (DA)

que es lo mismo que:

(B + C + D) – 3A

Quería escribir la expresión en este formulario porque se puede calcular en [math] \ Theta (1) [/ math] tiempo después del tiempo de preprocesamiento [math] \ Theta (n) [/ math] usando sumas de prefijo.

Desafortunadamente, no podemos ignorar los signos de valor absoluto … a menos que la siguiente condición sea verdadera: B, C y D son mayores o iguales que A.

Cuando esta condición es verdadera, entonces la expresión anterior funciona. Afortunadamente, podemos ordenar la entrada para asegurarnos de que la condición anterior sea siempre cierta.

Algoritmo

Eso nos deja con el siguiente algoritmo:

  1. Ordene la matriz en orden ascendente ([matemática] O (n \ lg n) [/ matemática] tiempo).
  2. Calcule sumas de prefijos para la matriz ([matemática] \ Theta (n) [/ matemática] tiempo).
  3. Itere a través de la matriz (índice i) y calcule la suma de la diferencia absoluta para todos los pares a partir de la matriz [i] usando la fórmula: (prefixSums [n – 1] – prefixSums [i]) – (array [i] * (n – i – 1)) ([matemáticas] \ Theta (n) [/ matemáticas] tiempo)
  4. Devuelve la suma de las sumas de diferencia absoluta ([matemática] \ Theta (1) [/ matemática] hora)

La complejidad general del tiempo de ejecución es [matemática] O (n \ lg n) [/ matemática] tiempo. Si la entrada ya está ordenada, el algoritmo se ejecuta en tiempo [matemático] \ Theta (n) [/ matemático]. No estoy seguro de si este es el algoritmo más eficiente … puede haber un algoritmo [matemático] \ Theta (n) [/ matemático].

La complejidad general del espacio es [matemática] \ Theta (n) [/ matemática].

Código Java

import java.util.Arrays; public class AbsoluteDifferenceSum {
public static void main(String[] args) {
int[] array = { 3, -2, -5, 18 };
int absoluteDifferenceSum = findAbsoluteDifferenceSum(array); System.out.println(absoluteDifferenceSum);
} public static int findAbsoluteDifferenceSum(int[] array) {
if (array == null || array.length == 0) {
throw new IllegalArgumentException("Array cannot be null or empty!");
} int n = array.length;
int[] arrayCopy = new int[n]; System.arraycopy(array, 0, arrayCopy, 0, n);
Arrays.sort(arrayCopy); int[] prefixSums = new int[n];
prefixSums[0] = arrayCopy[0]; for (int i = 1; i < n; ++i) {
prefixSums[i] = prefixSums[i - 1] + arrayCopy[i];
} int absoluteDifferenceSum = 0; for (int i = 0; i < n; ++i) {
int size = n - i - 1;
absoluteDifferenceSum += (prefixSums[n - 1] - prefixSums[i]) - (arrayCopy[i] * size);
} return absoluteDifferenceSum;
}
}

import java.util.Arrays; public class AbsoluteDifferenceSum {
public static void main(String[] args) {
int[] array = { 3, -2, -5, 18 };
int absoluteDifferenceSum = findAbsoluteDifferenceSum(array); System.out.println(absoluteDifferenceSum);
} public static int findAbsoluteDifferenceSum(int[] array) {
if (array == null || array.length == 0) {
throw new IllegalArgumentException("Array cannot be null or empty!");
} int n = array.length;
int[] arrayCopy = new int[n]; System.arraycopy(array, 0, arrayCopy, 0, n);
Arrays.sort(arrayCopy); int[] prefixSums = new int[n];
prefixSums[0] = arrayCopy[0]; for (int i = 1; i < n; ++i) {
prefixSums[i] = prefixSums[i - 1] + arrayCopy[i];
} int absoluteDifferenceSum = 0; for (int i = 0; i < n; ++i) {
int size = n - i - 1;
absoluteDifferenceSum += (prefixSums[n - 1] - prefixSums[i]) - (arrayCopy[i] * size);
} return absoluteDifferenceSum;
}
}

import java.util.Arrays; public class AbsoluteDifferenceSum {
public static void main(String[] args) {
int[] array = { 3, -2, -5, 18 };
int absoluteDifferenceSum = findAbsoluteDifferenceSum(array); System.out.println(absoluteDifferenceSum);
} public static int findAbsoluteDifferenceSum(int[] array) {
if (array == null || array.length == 0) {
throw new IllegalArgumentException("Array cannot be null or empty!");
} int n = array.length;
int[] arrayCopy = new int[n]; System.arraycopy(array, 0, arrayCopy, 0, n);
Arrays.sort(arrayCopy); int[] prefixSums = new int[n];
prefixSums[0] = arrayCopy[0]; for (int i = 1; i < n; ++i) {
prefixSums[i] = prefixSums[i - 1] + arrayCopy[i];
} int absoluteDifferenceSum = 0; for (int i = 0; i < n; ++i) {
int size = n - i - 1;
absoluteDifferenceSum += (prefixSums[n - 1] - prefixSums[i]) - (arrayCopy[i] * size);
} return absoluteDifferenceSum;
}
}

import java.util.Arrays; public class AbsoluteDifferenceSum {
public static void main(String[] args) {
int[] array = { 3, -2, -5, 18 };
int absoluteDifferenceSum = findAbsoluteDifferenceSum(array); System.out.println(absoluteDifferenceSum);
} public static int findAbsoluteDifferenceSum(int[] array) {
if (array == null || array.length == 0) {
throw new IllegalArgumentException("Array cannot be null or empty!");
} int n = array.length;
int[] arrayCopy = new int[n]; System.arraycopy(array, 0, arrayCopy, 0, n);
Arrays.sort(arrayCopy); int[] prefixSums = new int[n];
prefixSums[0] = arrayCopy[0]; for (int i = 1; i < n; ++i) {
prefixSums[i] = prefixSums[i - 1] + arrayCopy[i];
} int absoluteDifferenceSum = 0; for (int i = 0; i < n; ++i) {
int size = n - i - 1;
absoluteDifferenceSum += (prefixSums[n - 1] - prefixSums[i]) - (arrayCopy[i] * size);
} return absoluteDifferenceSum;
}
}

import java.util.Arrays; public class AbsoluteDifferenceSum {
public static void main(String[] args) {
int[] array = { 3, -2, -5, 18 };
int absoluteDifferenceSum = findAbsoluteDifferenceSum(array); System.out.println(absoluteDifferenceSum);
} public static int findAbsoluteDifferenceSum(int[] array) {
if (array == null || array.length == 0) {
throw new IllegalArgumentException("Array cannot be null or empty!");
} int n = array.length;
int[] arrayCopy = new int[n]; System.arraycopy(array, 0, arrayCopy, 0, n);
Arrays.sort(arrayCopy); int[] prefixSums = new int[n];
prefixSums[0] = arrayCopy[0]; for (int i = 1; i < n; ++i) {
prefixSums[i] = prefixSums[i - 1] + arrayCopy[i];
} int absoluteDifferenceSum = 0; for (int i = 0; i < n; ++i) {
int size = n - i - 1;
absoluteDifferenceSum += (prefixSums[n - 1] - prefixSums[i]) - (arrayCopy[i] * size);
} return absoluteDifferenceSum;
}
}

import java.util.Arrays; public class AbsoluteDifferenceSum {
public static void main(String[] args) {
int[] array = { 3, -2, -5, 18 };
int absoluteDifferenceSum = findAbsoluteDifferenceSum(array); System.out.println(absoluteDifferenceSum);
} public static int findAbsoluteDifferenceSum(int[] array) {
if (array == null || array.length == 0) {
throw new IllegalArgumentException("Array cannot be null or empty!");
} int n = array.length;
int[] arrayCopy = new int[n]; System.arraycopy(array, 0, arrayCopy, 0, n);
Arrays.sort(arrayCopy); int[] prefixSums = new int[n];
prefixSums[0] = arrayCopy[0]; for (int i = 1; i < n; ++i) {
prefixSums[i] = prefixSums[i - 1] + arrayCopy[i];
} int absoluteDifferenceSum = 0; for (int i = 0; i < n; ++i) {
int size = n - i - 1;
absoluteDifferenceSum += (prefixSums[n - 1] - prefixSums[i]) - (arrayCopy[i] * size);
} return absoluteDifferenceSum;
}
}

import java.util.Arrays; public class AbsoluteDifferenceSum {
public static void main(String[] args) {
int[] array = { 3, -2, -5, 18 };
int absoluteDifferenceSum = findAbsoluteDifferenceSum(array); System.out.println(absoluteDifferenceSum);
} public static int findAbsoluteDifferenceSum(int[] array) {
if (array == null || array.length == 0) {
throw new IllegalArgumentException("Array cannot be null or empty!");
} int n = array.length;
int[] arrayCopy = new int[n]; System.arraycopy(array, 0, arrayCopy, 0, n);
Arrays.sort(arrayCopy); int[] prefixSums = new int[n];
prefixSums[0] = arrayCopy[0]; for (int i = 1; i < n; ++i) {
prefixSums[i] = prefixSums[i - 1] + arrayCopy[i];
} int absoluteDifferenceSum = 0; for (int i = 0; i < n; ++i) {
int size = n - i - 1;
absoluteDifferenceSum += (prefixSums[n - 1] - prefixSums[i]) - (arrayCopy[i] * size);
} return absoluteDifferenceSum;
}
}

import java.util.Arrays; public class AbsoluteDifferenceSum {
public static void main(String[] args) {
int[] array = { 3, -2, -5, 18 };
int absoluteDifferenceSum = findAbsoluteDifferenceSum(array); System.out.println(absoluteDifferenceSum);
} public static int findAbsoluteDifferenceSum(int[] array) {
if (array == null || array.length == 0) {
throw new IllegalArgumentException("Array cannot be null or empty!");
} int n = array.length;
int[] arrayCopy = new int[n]; System.arraycopy(array, 0, arrayCopy, 0, n);
Arrays.sort(arrayCopy); int[] prefixSums = new int[n];
prefixSums[0] = arrayCopy[0]; for (int i = 1; i < n; ++i) {
prefixSums[i] = prefixSums[i - 1] + arrayCopy[i];
} int absoluteDifferenceSum = 0; for (int i = 0; i < n; ++i) {
int size = n - i - 1;
absoluteDifferenceSum += (prefixSums[n - 1] - prefixSums[i]) - (arrayCopy[i] * size);
} return absoluteDifferenceSum;
}
}

import java.util.Arrays; public class AbsoluteDifferenceSum {
public static void main(String[] args) {
int[] array = { 3, -2, -5, 18 };
int absoluteDifferenceSum = findAbsoluteDifferenceSum(array); System.out.println(absoluteDifferenceSum);
} public static int findAbsoluteDifferenceSum(int[] array) {
if (array == null || array.length == 0) {
throw new IllegalArgumentException("Array cannot be null or empty!");
} int n = array.length;
int[] arrayCopy = new int[n]; System.arraycopy(array, 0, arrayCopy, 0, n);
Arrays.sort(arrayCopy); int[] prefixSums = new int[n];
prefixSums[0] = arrayCopy[0]; for (int i = 1; i < n; ++i) {
prefixSums[i] = prefixSums[i - 1] + arrayCopy[i];
} int absoluteDifferenceSum = 0; for (int i = 0; i < n; ++i) {
int size = n - i - 1;
absoluteDifferenceSum += (prefixSums[n - 1] - prefixSums[i]) - (arrayCopy[i] * size);
} return absoluteDifferenceSum;
}
}

import java.util.Arrays; public class AbsoluteDifferenceSum {
public static void main(String[] args) {
int[] array = { 3, -2, -5, 18 };
int absoluteDifferenceSum = findAbsoluteDifferenceSum(array); System.out.println(absoluteDifferenceSum);
} public static int findAbsoluteDifferenceSum(int[] array) {
if (array == null || array.length == 0) {
throw new IllegalArgumentException("Array cannot be null or empty!");
} int n = array.length;
int[] arrayCopy = new int[n]; System.arraycopy(array, 0, arrayCopy, 0, n);
Arrays.sort(arrayCopy); int[] prefixSums = new int[n];
prefixSums[0] = arrayCopy[0]; for (int i = 1; i < n; ++i) {
prefixSums[i] = prefixSums[i - 1] + arrayCopy[i];
} int absoluteDifferenceSum = 0; for (int i = 0; i < n; ++i) {
int size = n - i - 1;
absoluteDifferenceSum += (prefixSums[n - 1] - prefixSums[i]) - (arrayCopy[i] * size);
} return absoluteDifferenceSum;
}
}

import java.util.Arrays; public class AbsoluteDifferenceSum {
public static void main(String[] args) {
int[] array = { 3, -2, -5, 18 };
int absoluteDifferenceSum = findAbsoluteDifferenceSum(array); System.out.println(absoluteDifferenceSum);
} public static int findAbsoluteDifferenceSum(int[] array) {
if (array == null || array.length == 0) {
throw new IllegalArgumentException("Array cannot be null or empty!");
} int n = array.length;
int[] arrayCopy = new int[n]; System.arraycopy(array, 0, arrayCopy, 0, n);
Arrays.sort(arrayCopy); int[] prefixSums = new int[n];
prefixSums[0] = arrayCopy[0]; for (int i = 1; i < n; ++i) {
prefixSums[i] = prefixSums[i - 1] + arrayCopy[i];
} int absoluteDifferenceSum = 0; for (int i = 0; i < n; ++i) {
int size = n - i - 1;
absoluteDifferenceSum += (prefixSums[n - 1] - prefixSums[i]) - (arrayCopy[i] * size);
} return absoluteDifferenceSum;
}
}

Aquí hay un método realmente simple.

Primero, ordena la matriz. Digamos que la matriz ordenada se ve como [-5, 2, 3, 5]. Para considerar solo pares distintos, para cada elemento, combínelo solo con los elementos que vinieron antes. Eso significa que la contribución de 3 al resultado total es abs (3 – 2) + abs (3 – (-5)). Debido a que la matriz está ordenada, todos los elementos que vinieron antes son más pequeños o iguales, por lo que esto puede reescribirse con seguridad como 3 * 2 – (2 + (-5)). En términos generales, la contribución de arr [i] a la suma es arr [i] * i – sum (arr [0 … i-1]). Afortunadamente, podemos mantener el término de la suma a medida que avanzamos, evitando la recalculación costosa. Obtendremos la respuesta cuando sumamos las contribuciones de cada arr [i].

Según la idea anterior, el pseudocódigo se vería así:

arr = input.sorted ()

total = 0
arraySum = 0
para i = 0 … N-1:
total + = (arr [i] * i – arraySum)
arraySum + = arr [i]
retorno total

Este código se ejecuta en [math] O (n) [/ math] tiempo después de la clasificación, por lo que el algoritmo general es [math] O (n \ log n) [/ math], a menos que los datos que tiene faciliten la clasificación más rápido que eso.

Dependiendo del significado del término “pares distintos”, es posible que deba deducir la matriz antes de ejecutar el paso lineal sobre los datos. Es decir, si en una entrada como [1, 2, 1, 2, 3], no desea contar (2, 1) varias veces (aunque hay varias formas en que el par puede formarse), entonces debería simplemente deducir la entrada. Convierta [1, 2, 1, 2, 3] a [1, 2, 3].