好的,我不记得我是如何偶然发现这个话题的,但我想我已经设法找到了一种快速算法来解决这个问题。至少对于给定的数据。我的解决方案是在 JS 中,我感觉更舒服。它可以在不到 7 毫秒的时间内解决,但我相信如果需要更快,它可以简单地转码为 Haskell 或 C。
好的,首先以免看到解决方案...
function getSum(arr,sum){
function getCombinations(arr){
var len = arr.length,
subs = Array(Math.pow(2,len)).fill();
return subs.map((_,i) => { var j = -1,
k = i,
res = [];
while (++j < len ) {
k & 1 && res.push(arr[j]);
k = k >> 1;
}
return res;
}).slice(1);
}
function getPossibles(a,t){
var fo = a[0],
maxTry = Math.min(Math.floor(t/fo.n),fo.c);
return a.length > 1 ? Array(maxTry).fill()
.map((_,i) => ({n:fo.n, c:maxTry-i}))
.reduce((p,c) => (p.push(...getPossibles(a.slice(1),t-c.n*c.c).map(e => a.length > 2 ? [c,...e]
: [c,e])),p),[])
: [{n:fo.n, c: maxTry}];
}
var hash = arr.reduce((p,c) => (p[c] ? p[c]++ : p[c] = 1,p),{}),
condense = Object.keys(hash)
.reverse()
.map(k => ({n: k, c:hash[k]}));
combos = getCombinations(condense);
possibles = combos.reduce((p,c) => (c.length > 1 ? p.push(...getPossibles(c,sum))
: p.push(getPossibles(c,sum)),p),[]);
return possibles.filter(p => p.reduce((s,o) => s += o.n*o.c,0) === sum)
.map(e => e.reduce((p,c) => p.concat(Array(c.c).fill(c.n)),[]));
}
var arr = [978, 978, 978, 978, 978, 978, 978, 978, 978, 978, 978, 978, 978, 978, 978, 978, 978, 978, 978, 978, 696, 696, 696, 696, 696, 696, 696, 696 ,696, 696, 696, 696, 696, 696, 696, 696, 696, 696, 696, 696, 678, 678, 678, 678, 678, 678, 678, 678, 678, 678, 446, 446, 446, 446, 446, 446, 446, 446, 446, 446],
result = [],
ps = 0,
pe = 0;
ps = performance.now();
result = getSum(arr,6000);
pe = performance.now();
console.log(result);
console.log("Done in:", pe-ps);
我试图使代码尽可能明确。我将尝试在这里逐步解释。
我们有一个函数来解决这个问题getSum()
,其中我们有两个实用函数,分别是getCombinations()
和getPossibles()
。
一旦我们收到我们的值数组(arr
)和目标(sum
),我们首先在下面建立一个哈希表hash
,结果是这样的;
{ '446': 10, '678': 10, '696': 20, '978': 20 }
显然,通过告诉我们哪个数字存在多少次,我们可以得到数组的映射。
然后我将数组重新映射为更简单的对象形式并将其存储condense
为
[ { n: '978', c: 20 },
{ n: '696', c: 20 },
{ n: '678', c: 10 },
{ n: '446', c: 10 } ]
此时我们需要得到这些对象的组合,以便以后解决所有的可能性。对于这个任务,我们使用了getCombinations()
效用函数。结果如下;
[ [ { n: '978', c: 20 } ],
[ { n: '696', c: 20 } ],
[ { n: '978', c: 20 }, { n: '696', c: 20 } ],
[ { n: '678', c: 10 } ],
[ { n: '978', c: 20 }, { n: '678', c: 10 } ],
[ { n: '696', c: 20 }, { n: '678', c: 10 } ],
[ { n: '978', c: 20 }, { n: '696', c: 20 }, { n: '678', c: 10 } ],
[ { n: '446', c: 10 } ],
[ { n: '978', c: 20 }, { n: '446', c: 10 } ],
[ { n: '696', c: 20 }, { n: '446', c: 10 } ],
[ { n: '978', c: 20 }, { n: '696', c: 20 }, { n: '446', c: 10 } ],
[ { n: '678', c: 10 }, { n: '446', c: 10 } ],
[ { n: '978', c: 20 }, { n: '678', c: 10 }, { n: '446', c: 10 } ],
[ { n: '696', c: 20 }, { n: '678', c: 10 }, { n: '446', c: 10 } ],
[ { n: '978', c: 20 },
{ n: '696', c: 20 },
{ n: '678', c: 10 },
{ n: '446', c: 10 } ] ]
所以现在我们知道我们将尝试什么。但我们必须巧妙地尝试。我们知道目标总和是 6000,我们不应该允许过多的计算。棘手的部分来了。我们有getPossibles()
实用函数,它将只返回计数(c
属性)值总和小于 6000 的对象。好吧,我受到了我的Array.prototype.cartesian()工具的影响,它工作得很好。
OkgetPossibles()
将获取一个对象数组(压缩数据),并为我们提供总和小于 6000 的集合的可能组合。例如,对于[ { n: '978', c: 20 }, { n: '696', c: 20 }, { n: '678', c: 10 } ]
我们将收到的组合;
[ [ { n: '978', c: 5 }, { n: '696', c: 1 }, { n: '678', c: 0 } ],
[ { n: '978', c: 4 }, { n: '696', c: 3 }, { n: '678', c: 0 } ],
[ { n: '978', c: 4 }, { n: '696', c: 2 }, { n: '678', c: 1 } ],
[ { n: '978', c: 4 }, { n: '696', c: 1 }, { n: '678', c: 2 } ],
[ { n: '978', c: 3 }, { n: '696', c: 4 }, { n: '678', c: 0 } ],
[ { n: '978', c: 3 }, { n: '696', c: 3 }, { n: '678', c: 1 } ],
[ { n: '978', c: 3 }, { n: '696', c: 2 }, { n: '678', c: 2 } ],
[ { n: '978', c: 3 }, { n: '696', c: 1 }, { n: '678', c: 3 } ],
[ { n: '978', c: 2 }, { n: '696', c: 5 }, { n: '678', c: 0 } ],
[ { n: '978', c: 2 }, { n: '696', c: 4 }, { n: '678', c: 1 } ],
[ { n: '978', c: 2 }, { n: '696', c: 3 }, { n: '678', c: 2 } ],
[ { n: '978', c: 2 }, { n: '696', c: 2 }, { n: '678', c: 3 } ],
[ { n: '978', c: 2 }, { n: '696', c: 1 }, { n: '678', c: 4 } ],
[ { n: '978', c: 1 }, { n: '696', c: 7 }, { n: '678', c: 0 } ],
[ { n: '978', c: 1 }, { n: '696', c: 6 }, { n: '678', c: 1 } ],
[ { n: '978', c: 1 }, { n: '696', c: 5 }, { n: '678', c: 2 } ],
[ { n: '978', c: 1 }, { n: '696', c: 4 }, { n: '678', c: 3 } ],
[ { n: '978', c: 1 }, { n: '696', c: 3 }, { n: '678', c: 4 } ],
[ { n: '978', c: 1 }, { n: '696', c: 2 }, { n: '678', c: 5 } ],
[ { n: '978', c: 1 }, { n: '696', c: 1 }, { n: '678', c: 6 } ] ]
嗯……一旦你手头有这些数据,那么减少你所拥有的就是一件简单的事情。所以只有最后一行;
possibles = combos.reduce((p,c) => (c.length > 1 ? p.push(...getPossibles(c,sum))
: p.push(getPossibles(c,sum)),p),[]);
.filter(p => p.reduce((s,o) => s += o.n*o.c,0) === sum)
.map(e => e.reduce((p,c) => p.concat(Array(c.c).fill(c.n)),[]));
是我们如何在 JS 中在不到 7 毫秒的时间内获得解决方案。