Skip to content

Commit

Permalink
Agregar sol a sum diferencias absolutas (#10)
Browse files Browse the repository at this point in the history
  • Loading branch information
Diego committed Sep 25, 2021
1 parent d98a1ce commit 7d0b982
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 1 deletion.
29 changes: 29 additions & 0 deletions python/sols/arrays/prefix_sum_problems.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from typing import List


def sum_absolute_differences(nums: List[int]) -> List[int]:
"""Return sum of absolute differences of each element.
For example, if we have the input [2, 3, 5], the result would be:
- result[0] = |2 - 3| + |2 - 5| = 4
- result[1] = |3 - 2| + |3 - 5| = 3
- result[2] = |5 - 2| + |5 - 3| = 5
:param nums: Input array
:type nums: List[int]
:return: Result with sum of absolute differences
:rtype: List[int]
"""
n = len(nums)
prefix = [0]*(n + 1)
for i in range(1, len(nums) + 1):
prefix[i] = prefix[i - 1] + nums[i - 1]

result = [0]*n
for i in range(n):
result[i] = (
prefix[n] - prefix[i + 1] - nums[i]*(n - i - 1)
- prefix[i] + nums[i]*i
)
return result
6 changes: 6 additions & 0 deletions python/tests/arrays/test_prefix_sum_problems.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from sols.arrays.prefix_sum_problems import sum_absolute_differences


def test_sum_absolute_differences():
assert sum_absolute_differences([2, 3, 5]) == [4, 3, 5]

65 changes: 64 additions & 1 deletion sols-expl/arrays/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,5 +124,68 @@ algoritmo spiral-iterativo

Este enfoque es menos propenso a errores al ser programado.

## Suma de diferencias absolutas [#10][i10]

[i3]: https://github.com/dpalmasan/code-challenges/issues/3
### Enfoque Naïve

Podemos resolver el problema a fuerza bruta, iterando por cada elemento para calcular el elemento en el `array` de salida. El algoritmo sería:

```
algoritmo naive-sum-abs-diff
entrada: nums: int[]
salida: result: int[]
N = nums.length
result = new int[N]
for i = 0 to N - 1:
for j = 0 to N - 1:
if i == j:
continue
result[i] += |nums[i] - nums[j]|
return result
```

Este algoritmo tiene una complejidad `O(n^2)`.

* ¿Podemos hacerlo más eficiente?
* ¿Estamos utilizando toda la información?

### Enfoque optimizado

¿Qué información extra tenemos en el problema? Se dice que el `array` se encuentra ordenado, ¿Se puede usar esta información para hacer un algoritmo más eficiente?

Probemos con el siguiente ejemplo: `[1, 5, 10, 13]`. El resultado debiese ser:

* `result[0] = |1 - 5| + |1 - 10| + |1 - 13| = 25`
* `result[1] = |5 - 1| + |5 - 10| + |5 - 13| = 17`
* `result[2] = |10 - 1| + |10 - 5| + |10 - 13| = 17`
* `result[3] = |13 - 1| + |13 - 5| + |13 - 10| = 23`

También tenemos que la suma total de los elementos es `1 + 5 + 10 + 13 = 29`, y la suma acumulada es `[0, 1, 6, 16, 29]`. Por otro lado la suma de las diferencias absolutas siempre será `<=` que la suma de los elementos, ya que los elementos son todos positivos. Como los elementos están ordenados de forma creciente, desde `0` hasta el elemento `i`, hemos agregado a la suma `i * nums[i]`, por ejemplo en el caso `results[2]`, tenemos `|10 - 1| + |10 -5| = 10 - 1 + 10 - 5 = 2 * 10 - 1 - 5`. También podemos observar que estamos restando la suma acumulada hasta el elemento `i`, siguiendo el ejemplo tenemos `|10 - 1| + |10 -5| = 2 * 10 - (1 + 5)`. También observamos que desde `i` en adelante estamos quitando `n - i - 1` veces el elemento `nums[i]`. Por ejemplo, para el caso `i = 0`, tenemos `|1 - 5| + |1 - 10| + |1 - 13| = 5 + 10 + 13 - 1 - 1 - 1 = 5 + 10 + 13 - (4 - 0 - 1) * 1`. El término `5 + 10 + 13` es la suma acumulada desde `i + 1` en adelante. Finalmente, generalizando llegamos a:

* `result[i] = cumsum[n] - cumsum[i + 1] - nums[i] * (n - i - 1) - cumsum[i] + nums[i] * i`

Por lo tanto, llegamos al siguiente algoritmo:

```
algoritmo sum-abs-diff
entrada: nums: int[]
salida: result: int[]
# cumsum se inicializa con ceros
cumsum = new int[N + 1]
for i in 1 to N:
cumsum[i] = cumsum[i - 1] + nums[i - 1]
result = new int[nums.length]
for i = 0 to N - 1:
result[i] = cumsum[n] - cumsum[i + 1] - nums[i] * (n - i - 1) - cumsum[i] + nums[i] * i
return result
```

En este caso podemos observar un intercambio memoria/tiempo de ejecución, ya que ahora la complejidad en tiempo de ejecución es `O(N)`, y la complejidad en memoria es también `O(N)` ya que necesitamos pre-calcular la suma acumulada.

[i3]: https://github.com/dpalmasan/code-challenges/issues/3
[i10]: https://github.com/dpalmasan/code-challenges/issues/10

0 comments on commit 7d0b982

Please sign in to comment.