问题 (采用二进制正序表示法)
从一个数组中找出N个数,其和为M的所有可能。
eg: 从数组 [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)
// => [[1, 4], [2, 5]]
实现思路
总体思路: 根据数组长度构建二进制数据,再选择其中满足条件的数据。
我们用 1 和 0 来表示数组中的某位元素是否选中。因此,可以用 0110 来表示数组中第 1 位和 第 2 位 被选中了。
下面列一下长度为 4 的所有二进制数据表示情况:
- 0000 表示没有选择数组中的任何元素
- 0001 表示选择了数组中第 3 位元素
- 0010 表示选择了数组中第 2 位元素
- 0011 表示选择了数组中第 2、3位元素
- 0100 表示选择了数组中第 1 位元素
- 0101 表示选择了数组中第 1、3 位元素
- 0110 表示选择了数组中第 1、2 位元素
- 0111 表示选择了数组中第 1、2、3 位元素
- 1000 表示选择了数组中第 0 位元素
- 1001 表示选择了数组中第 0、3 位元素
- 1010 表示选择了数组中第 0、2 位元素
- 1011 表示选择了数组中第 0、2、3 位元素
- 1100 表示选择了数组中第 0、1 位元素
- 1101 表示选择了数组中第 0、1、3 位元素
- 1110 表示选择了数组中第 0、1、2 位元素
- 1111 表示选择了数组中所有位元素
那么开篇数组,满足条件的二进制(存在两个1)有 0011、0101、0110、1001、1010、1100 六种可能。而符合对应元素之和为 5 的只有 0110 和 1001.
思路就是构建了所有长度为 4 的二进制,再找到符合条件的二进制。
题目的条件有两个。
- 被选中的个数是 2.
- 被选中的和是 5.
即 遍历所有的二进制,判断选中的个数是否为 2,然后再求对应的元素之和是否为 5.
第一个问题,如何遍历所有二进制数据?
数组长度为 4,对应16, 即 1 << 4。
注意 1 << 31 为-2147483648,可以使用Math.pow(2, 31)来代替
第二个问题,如何求取被选中的元素的个数呢?即求取二进制字符串中 1 的个数?
- 实现方式一:
const n = num => num.toString(2).replace(/0/g, '').length
// console.log(n(3)); // 2 3的二进制: 0011
num.toString(2)
将 num 装换成二进制
- 实现方式二:
function search(i) {
let count = 0
while(i) {
// i & 1 与运算 结果为 二进制中含有 1 的
if (i & 1) {
++count
}
// 位运算 - 右移一位
i >>= 1;
}
return count
}
// console.log(search(0b1010)); // 2
上述算法的思路其实很简单,将二进制逐步右移 1 位,看看末尾为 1 的个数。比如 10 的二进制是 1010,逐步右移的所有可能是 1010->101->10->1->0,其中有 2 次末尾是 1。因此结果是 2。
第三个问题,如何根据二进制数据来求和?
比如 0110, 应该求和 arr[1] + arr[2].
问题转化成了如何判断数组下标是否在 0110 中呢?
分析: 0110,我们把 1 一次左移 << (0,1, 2, 3)次,即对应 arr 数组的下标。然后一次判断:
1 << 0 => 0001
1 << 1 => 0010
1<< 2 => 0100
1<< 3 => 1000
然后依次 &
运算。
0110 & 0001 => 0
0110 * 0010 => 0010 => 2
0110 * 0100 => 0100 => 3
0110 * 1000 => 0 => 0
所以拿到对应的下标 arr[1] = 2, arr[2] = 3
实现:
var arr = [1,2,3,4]
var s = 0, temp = [], len = arr.length;
for (var i = 0,; 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]]
时间复杂度 2^n 指数增加🙀