¿Cómo haría para probar todas las combinaciones posibles de adiciones de un conjunto N
de números dado para que sumen un número final dado?
Un breve ejemplo:
- Conjunto de números para agregar:
N = {1,5,22,15,0,...}
- Resultado deseado:
12345
¿Cómo haría para probar todas las combinaciones posibles de adiciones de un conjunto N
de números dado para que sumen un número final dado?
Un breve ejemplo:
N = {1,5,22,15,0,...}
12345
Respuestas:
Este problema se puede resolver con una combinación recursiva de todas las sumas posibles filtrando las que alcanzan el objetivo. Aquí está el algoritmo en Python:
Este tipo de algoritmos se explican muy bien en la siguiente conferencia de programación abstracta de Standford : este video es muy recomendable para comprender cómo funciona la recursividad para generar permutaciones de soluciones.
Editar
Lo anterior como una función generadora, lo que lo hace un poco más útil. Requiere Python 3.3+ debido a
yield from
.Aquí está la versión Java del mismo algoritmo:
Es exactamente la misma heurística. Mi Java está un poco oxidado, pero creo que es fácil de entender.
Conversión de C # de la solución Java: (por @JeremyThompson)
Solución de rubí: (por @emaillenin)
Editar: discusión de complejidad
Como otros mencionan, este es un problema NP-difícil . Se puede resolver en tiempo exponencial O (2 ^ n), por ejemplo para n = 10 habrá 1024 posibles soluciones. Si los objetivos que intenta alcanzar están en un rango bajo, entonces este algoritmo funciona. Entonces, por ejemplo:
subset_sum([1,2,3,4,5,6,7,8,9,10],100000)
genera 1024 ramas porque el objetivo nunca puede filtrar posibles soluciones.Por otro lado,
subset_sum([1,2,3,4,5,6,7,8,9,10],10)
genera solo 175 ramas, porque el objetivo a alcanzar10
puede filtrar muchas combinaciones.Si
N
yTarget
son números grandes, uno debería pasar a una versión aproximada de la solución.fuente
[1, 2, 0, 6, -3, 3], 3
que solo[1,2], [0,3], [3]
[6, -3, 3]
[1, 2, 5], 5
sólo salidas[5]
, cuando[1, 1, 1, 1, 1]
,[2, 2, 1]
y[2, 1, 1, 1]
son soluciones.i+1
inremaining = numbers[i+1:]
. Parece que ese algoritmo no permite duplicados.[1, 1, 3]
echar un vistazo a stackoverflow.com/a/34971783/3684296 (Python)La solución de este problema se ha dado un millón de veces en Internet. El problema se llama El problema del cambio de monedas . Se pueden encontrar soluciones en http://rosettacode.org/wiki/Count_the_coins y el modelo matemático del mismo en http://jaqm.ro/issues/volume-5,issue-2/pdfs/patterson_harmel.pdf (o cambio de moneda de Google problema )
Por cierto, la solución Scala de Tsagadai es interesante. Este ejemplo produce 1 o 0. Como efecto secundario, enumera en la consola todas las soluciones posibles. Muestra la solución, pero falla al hacerla utilizable de alguna manera.
Para ser lo más útil posible, el código debe devolver un
List[List[Int]]
para permitir obtener el número de solución (longitud de la lista de listas), la "mejor" solución (la lista más corta) o todas las soluciones posibles.Aquí hay un ejemplo. Es muy ineficiente, pero es fácil de entender.
Cuando se ejecuta, muestra:
La
sumCombinations()
función se puede usar por sí misma, y el resultado se puede analizar más a fondo para mostrar la "mejor" solución (la lista más corta) o el número de soluciones (el número de listas).Tenga en cuenta que incluso así, los requisitos pueden no cumplirse por completo. Puede suceder que el orden de cada lista en la solución sea significativo. En tal caso, cada lista tendría que duplicarse tantas veces como haya una combinación de sus elementos. O podríamos estar interesados solo en las combinaciones que son diferentes.
Por ejemplo, podríamos considerar que
List(5, 10)
debería dar dos combinaciones:List(5, 10)
yList(10, 5)
. ParaList(5, 5, 5)
ello podría dar tres combinaciones o solo una, dependiendo de los requisitos. Para enteros, las tres permutaciones son equivalentes, pero si estamos tratando con monedas, como en el "problema del cambio de monedas", no lo son.Tampoco se establece en los requisitos la cuestión de si cada número (o moneda) se puede usar solo una o muchas veces. Podríamos (¡y deberíamos!) Generalizar el problema a una lista de listas de ocurrencias de cada número. Esto se traduce en la vida real en "cuáles son las formas posibles de ganar una cierta cantidad de dinero con un conjunto de monedas (y no un conjunto de valores de monedas)". El problema original es solo un caso particular de este, donde tenemos tantas ocurrencias de cada moneda como sea necesario para hacer la cantidad total con cada valor de moneda.
fuente
En Haskell :
Y J :
Como puede observar, ambos adoptan el mismo enfoque y dividen el problema en dos partes: generan cada miembro del conjunto de poder y verifican la suma de cada miembro al objetivo.
Hay otras soluciones, pero esta es la más sencilla.
¿Necesita ayuda con cualquiera de ellos o para encontrar un enfoque diferente?
fuente
not in scope: 'subsequences'
algún puntero?import Data.List
Una versión de Javascript:
fuente
remaining = numbers.slice();
remaining.slice(i + 1);
contrario deberíanumbers.slice(i + 1);
cambiar la matriz de númerosslice
devuelve una copia (superficial), no modifica lanumbers
matriz.Versión C ++ del mismo algoritmo
fuente
Versión C # de respuesta de código @msalvadores
fuente
Pensé que usaría una respuesta de esta pregunta, pero no pude, así que aquí está mi respuesta. Está utilizando una versión modificada de una respuesta en Estructura e interpretación de programas de computadora . Creo que esta es una mejor solución recursiva y debería complacer más a los puristas.
Mi respuesta está en Scala (y disculpas si mi Scala apesta, acabo de comenzar a aprenderlo). La locura de findSumCombinations es ordenar y singularizar la lista original de la recursión para evitar engaños.
Para usarlo:
fuente
He convertido la lógica anterior de Python a PHP.
fuente
Otra solución de Python sería usar el
itertools.combinations
módulo de la siguiente manera:Salida:
[(8, 7), (5, 10), (3, 8, 4), (3, 5, 7)]
fuente
Aquí hay una solución en R
fuente
subset_sum(numbers = c(1:2), target = 5)
vuelve"sum(1+2+2)=5"
. Pero falta la combinación 1 + 1 + 1 + 1 + 1. Al establecer objetivos a números más altos (por ejemplo, 20) faltan aún más combinaciones.subset_sum(1:2, 4)
no debería devolver soluciones porque no hay una combinación de 1 y 2 que se sume a 4. Lo que debe agregarse a mi función es un escape sii
es mayor que la longitud denumbers
Aquí hay una versión de Java que es adecuada para N pequeña y suma objetivo muy grande, cuando la complejidad
O(t*N)
(la solución dinámica) es mayor que el algoritmo exponencial. Mi versión utiliza un encuentro en el medio ataque, junto con un poco de cambio para reducir la complejidad del clásico ingenuoO(n*2^n)
alO(2^(n/2))
.Si desea usar esto para conjuntos con entre 32 y 64 elementos, debe cambiar el
int
que representa el subconjunto actual en la función de paso a un,long
aunque el rendimiento obviamente disminuirá drásticamente a medida que aumente el tamaño del conjunto. Si desea usar esto para un conjunto con un número impar de elementos, debe agregar un 0 al conjunto para que sea un número par.fuente
Esto es similar a un problema de cambio de moneda
fuente
Algoritmo muy eficiente usando tablas que escribí en C ++ hace un par de años.
Si configura PRINT 1 imprimirá todas las combinaciones (pero no utilizará el método eficiente).
Es tan eficiente que calcula más de 10 ^ 14 combinaciones en menos de 10 ms.
fuente
Excel VBA versión a continuación. Necesitaba implementar esto en VBA (no es mi preferencia, ¡no me juzguen!), Y utilicé las respuestas en esta página para el enfoque. Estoy cargando en caso de que otros también necesiten una versión de VBA.
La salida (escrita en la ventana Inmediato) debe ser:
fuente
Aquí hay una mejor versión con mejor formato de salida y características de C ++ 11:
fuente
Versión no recursiva de Java que simplemente sigue agregando elementos y redistribuyéndolos entre los posibles valores.
0
Se ignoran y funciona para listas fijas (lo que se le da es con lo que puede jugar) o una lista de números repetibles.Entrada de muestra:
Salida de muestra:
fuente
Para encontrar las combinaciones usando Excel - (es bastante fácil). (Tu computadora no debe ser demasiado lenta)
Descargue el archivo de Excel "Sum to Target".
Siga las instrucciones en la página del sitio web.
espero que esto ayude.
fuente
Conversión Swift 3 de solución Java: (por @JeremyThompson)
uso:
fuente
Esto también se puede usar para imprimir todas las respuestas
La complejidad del tiempo es exponencial. Orden de 2 ^ n
fuente
Estaba haciendo algo similar para una asignación de escala. Pensé en publicar mi solución aquí:
fuente
Porté la muestra de C # a Objective-c y no la vi en las respuestas:
fuente
Respuesta de @ KeithBeller con nombres de variables ligeramente modificados y algunos comentarios.
fuente
Versión PHP , inspirada en la versión C # de Keith Beller.
La versión PHP de bala no me funcionó porque no necesitaba agrupar números. Quería una implementación más simple con un valor objetivo y un grupo de números. Esta función también eliminará cualquier entrada duplicada.
fuente
Recomendado como respuesta:
Aquí hay una solución con generadores es2015 :
El uso de generadores en realidad puede ser muy útil porque le permite pausar la ejecución del script inmediatamente al encontrar un subconjunto válido. Esto contrasta con las soluciones sin generadores (es decir, sin estado) que tienen que iterar a través de cada subconjunto de
numbers
fuente
Deduce 0 en primer lugar. Cero es una identidad para agregar, por lo que es inútil para las leyes de monoides en este caso particular. También deduzca números negativos también si desea subir a un número positivo. De lo contrario, también necesitaría una operación de resta.
Entonces ... el algoritmo más rápido que puede obtener en este trabajo en particular es el siguiente dado en JS.
Este es un algoritmo muy rápido, pero si ordena la
data
matriz descendente , será aún más rápido. El uso.sort()
es insignificante ya que el algoritmo terminará con invocaciones mucho menos recursivas.fuente
Versión de Perl (de la respuesta principal):
Resultado:
Versión Javascript:
Javascript one-liner que realmente devuelve resultados (en lugar de imprimirlo):
Y mi favorito, una línea con devolución de llamada:
fuente
Esta es una solución dinámica para que JS indique cuántas formas puede obtener una suma determinada. Esta puede ser la solución correcta si piensa en la complejidad del tiempo y el espacio.
fuente
fuente
No me gustó la solución Javascript. Vi por qué construyo uno para myselft usando aplicaciones parciales, cierres y recursividad:
Ok, me preocupaba principalmente si la matriz de combinaciones podría satisfacer el objetivo requerido, pero con esto enfocado puede comenzar a encontrar el resto de combinaciones
Aquí solo establece el objetivo y pasa la matriz de combinaciones.
la implementación actual se me ocurrió
fuente