每日算法:N数之和
共 4242字,需浏览 9分钟
·
2021-09-02 21:58
回复交流,加入前端编程面试算法每日一题群
请用算法实现,从给定的无需、不重复的数组A中,取出N个数,使其相加和为M。并给出算法的时间、空间复杂度,如:
var arr = [1, 4, 7, 11, 9, 8, 10, 6];
var N = 3;
var M = 27;
Result:
[7, 11, 9], [11, 10, 6], [9, 8, 10]
解题思路:利用二进制
根据数组长度构建二进制数据,再选择其中满足条件的数据。
我们用 1
和 0
来表示数组中某位元素是否被选中。因此,可以用 0110
来表示数组中第 1
位和第 2
位被选中了。
所以,本题可以解读为:
数组中被选中的个数是 N
。被选中的和是 M
。
我们的算法思路逐渐清晰起来:遍历所有二进制,判断选中个数是否为 N
,然后再求对应的元素之和,看其是否为 M
。
1. 从数组中取出 N 个数
例如:
var arr = [1, 2, 3, 4];
var N = 3;
var M = 6;
如何判断 N=3
是,对应的选取二进制中有几个 1
喃?
最简单的方式就是:
const n = num => num.toString(2).replace(/0/g, '').length
这里我们尝试使用一下位运算来解决本题,因为位运算是不需要编译的(位运算直接用二进制进行表示,省去了中间过程的各种复杂转换,提高了速度),肯定速度最快。
我们知道 1&0=0
、 1&1=1
,1111&1110=1110
,即 15&14=14
,所以我们每次 &
比自身小 1
的数都会消除一个 1
,所以这里建立一个迭代,通过统计消除的次数,就能确定最终有几个 1 了:
const n = num => {
let count = 0
while(num) {
num &= (num - 1)
count++
}
return count
}
2. 和为 M
现在最后一层判断就是选取的这些数字和必须等于 M
,即根据 N
生成的对应二进制所在元素上的和 是否为 M
比如 1110
,我们应该判断 arr[0] + arr[1] + arr[2]
是否为 M
。
那么问题也就转化成了如何判断数组下标是否在 1110
中呢?如何在则求和
其实也很简单,比如下标 1
在,而下标 3
不在。我们把 1
转化成 0100
, 1110 & 0100
为 1100
, 大于 0
,因此下标 1
在。而 1110 & 0001
为 0
,因此 下标 3
不在。
所以求和我们可以如下实现:
let arr = [1,2,3,4]
// i 为满足条件的二进制
let i = 0b1110
let s = 0, temp = []
let len = arr.length
for (let j = 0; j < len; j++) {
if ( i & 1 << (len - 1 - j)) {
s += arr[j]
temp.push(arr[j])
}
}
console.log(temp)
// => [1, 2, 3]
最终实现
// 参数依次为目标数组、选取元素数目、目标和
const search = (arr, count, sum) => {
// 计算某选择情况下有几个 1,也就是选择元素的个数
const getCount = num => {
let count = 0
while(num) {
num &= (num - 1)
count++
}
return count
}
let len = arr.length, bit = 1 << len, res = []
// 遍历所有的选择情况
for(let i = 1; i < bit; i++){
// 满足选择的元素个数 === count
if(getCount(i) === count){
let s = 0, temp = []
// 每一种满足个数为 N 的选择情况下,继续判断是否满足 和为 M
for(let j = 0; j < len; j++){
// 建立映射,找出选择位上的元素
if(i & 1 << (len - 1 -j)) {
s += arr[j]
temp.push(arr[j])
}
}
// 如果这种选择情况满足和为 M
if(s === sum) {
res.push(temp)
}
}
}
return res
}
最后
号内回复:
120
套模版