特此说明,本文算法改自于《从一个数组中找出 N 个数,其和为 M 的全部可能--最 nice 的解法》一文。本文不一样的是,采用二进制正序表示法,这种实现思路更直观、更简单些。javascript
从一个数组中找出 N 个数,其和为 M 的全部可能。 java
举个例子,从数组 [1, 2, 3, 4] 中选取 2 个元素,求和为 5 的全部可能。答案是两组组合: 1,4 和 2,3。算法
假设封装函数为 search
:数组
function search(arr, count, sum) {
...
return res
}
复制代码
则有,函数
search([1,2,3,4],2,5)
// => [[2,3],[1,4]]
复制代码
这里咱们简单说一下整体思路:根据数组长度构建二进制数据,再选择其中知足条件的数据。post
咱们用 1 和 0 来表示数组中某位元素是否被选中。所以,能够用 0110 来表示数组中第 1 位和第 2 位被选中了。ui
下面列一下长度为 4 的全部二进制数据表示状况:spa
那么开篇的例子, 4 选 2,知足条件的二进制有 00十一、010一、01十、100一、10十、1100 共 6 种可能。而符合对应元素之和为 5 的只有 0110 和 1001。code
看到了吗,思路是咱们构建了全部长度为 4 的二进制,再找到符合条件的二进制。ip
这里条件有两个。
咱们的算法思路逐渐清晰起来: 遍历全部二进制,判断选中个数是否为 2,而后再求对应的元素之和,看其是否为 5。
这个难不到咱们,数组长度为 4,那么全部二进制数据是 0 - 15。
for (var i = 0; i < 16; i++) {
...
}
复制代码
数组长度 4,对应16,即 1 << 4。
注意 1 << 31 为-2147483648,可使用Math.pow(2, 31)来代替
实现方式有多种,好比其中一种是:
function n(i) {
var count = 0;
while( i ) {
if(i & 1){
++count;
}
i >>= 1;
}
return count;
}
console.log(n(0b1010))
// => 2
复制代码
上述算法的思路其实很简单,将二进制逐步右移 1 位,看看末尾为 1 的个数。好比 10 的二进制是 1010,逐步右移的全部多是 1010->101->10->1->0,其中有 2 次末尾是 1。所以结果是 2。
好比 0110,咱们应该求和 arr[1] + arr[2]。
问题转化成了如何判断数组下标是否在 0110 中呢?
其实也很简单,好比下标 1 在,而下标 3 不在。咱们把 1 转化成 0100,0110 & 0100 为 0100, 大于 0,所以下标 1 在。而 0110 & 0001 为 0,所以 下标 3 不在。
因此求和咱们能够以下实现:
var arr = [1,2,3,4]
var s = 0, temp = [];
for (var i = 0, len = arr.length; i < len; i++) {
if ( 0b0110 & 1 << (len - 1 - i)) {
s += arr[i]
temp.push(arr[i])
}
}
console.log(temp)
// => [2,3]
复制代码
有了以上铺垫,这里给出最终实现:
function search(arr, count, sum) {
var len = arr.length, res = [];
for (var i = 0; i < Math.pow(2, len); i++) {
if (n(i) == count) {
var s = 0, temp = [];
for (var j = 0; j < len; j++) {
if (i & 1 << (len - 1 -j)) {
s += arr[j]
temp.push(arr[j])
}
}
if (s == sum) {
res.push(temp)
}
}
}
return res;
}
function n(i) {
var count = 0;
while( i ) {
if(i & 1){
++count;
}
i >>= 1;
}
return count;
}
console.log(search([1,2,3,4],2,5))
// => [[2,3],[1,4]]
复制代码
最后,能够看出其实不必引入各类概念就能够把本算法说清楚。
本文完。