1、两数之和
leetcode 精选100题(1)两数之和-简单_艾米栗写代码的博客-优快云博客_leetcode每日一题100题
2、三数之和
双指针法:在上一题数组合并中,也用到了双指针法。这里也是要用双指针法。
双指针,指的是在遍历对象的过程中,不是普通的使用单个指针进行访问,而是使用两个相同方向(快慢指针)或者相反方向(对撞指针)的指针进行扫描,从而达到相应的目的。换言之,双指针法充分使用了数组有序这一特征,从而在某些情况下能够简化一些运算。
/**
* @param {number[]} nums
* @return {number[][]}
*/
var threeSum = function(nums) {
let ans = [];
const len = nums.length;
if(nums == null || len < 3) return ans;
nums.sort((a, b) => a - b); // 排序
for (let i = 0; i < len ; i++) {
if(nums[i] > 0) break; // 如果当前数字大于0,则三数之和一定大于0,所以结束循环
if(i > 0 && nums[i] == nums[i-1]) continue; // 去重
let L = i+1;
let R = len-1;
while(L < R){
const sum = nums[i] + nums[L] + nums[R];
if(sum == 0){
ans.push([nums[i],nums[L],nums[R]]);
while (L<R && nums[L] == nums[L+1]) L++; // 去重
while (L<R && nums[R] == nums[R-1]) R--; // 去重
L++;
R--;
}
else if (sum < 0) L++;
else if (sum > 0) R--;
}
}
return ans;
};
3、多数之和
1、DFS 递归求解
利用递归求解,但是当数字一增多,估计会爆炸。
/**
* @param {number[]} nums
* @param {number} target
* @return {number[][]}
*/
var nSum = function(nums, target) {
const helper = (index, N, temp) => {
// 如果下标越界了或者 N < 3 就没有必要在接着走下去了
if (index === len || N < 3) {
return
}
for (let i = index; i < len; i++) {
// 剔除重复的元素
if (i > index && nums[i] === nums[i - 1]) {
continue
}
// 如果 N > 3 的话就接着递归
// 并且在递归结束之后也不走下边的逻辑
// 注意这里不能用 return
// 否则循环便不能跑完整
if (N > 3) {
helper(i + 1, N - 1, [nums[i], ...temp])
continue
}
// 当走到这里的时候,相当于在求「三数之和」了
// temp 数组在这里只是把前面递归加入的数组算进来
let left = i + 1
let right = len - 1
while (left < right) {
let sum = nums[i] + nums[left] + nums[right] + temp.reduce((prev, curr) => prev + curr)
if (sum === target) {
res.push([...temp, nums[i], nums[left], nums[right]])
while (left < right && nums[left] === nums[left + 1]) {
left++
}
while (left < right && nums[right] === nums[right - 1]) {
right--
}
left++
right--
} else if (sum < target) {
left++
} else {
right--
}
}
}
}
let res = []
let len = nums.length
nums.sort((a, b) => a - b)
helper(0, 4, [])
return res
};
2、位数求解。
字节:N数之和 · Issue #128 · sisterAn/JavaScript-Algorithms · GitHub
解题思路:利用二进制
根据数组长度构建二进制数据,再选择其中满足条件的数据。
我们用
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
为0100
, 大于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 }
3、哈希求解
前端进阶算法8:头条正在面的哈希表问题 · Issue #49 · sisterAn/JavaScript-Algorithms · GitHub