好的,我不记得我是如何偶然发现这个话题的,但我想我已经设法找到了一种快速算法来解决这个问题。至少对于给定的数据。我的解决方案是在 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 as 下
[ { 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() 工具的影响,该工具运行良好。
好的,getPossibles() 将获取一个对象数组(压缩数据),并为我们提供总和小于 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 毫秒得到解决方案。