经常会有人问,作为前端,你在实际工作中用到过哪些算法,而我回答一般是,树和位运算;
想想 webpack 上的那些依赖的版本类型,想想 react 源码中的那些 flag 的定义和运算,我觉得还是很有必要去学习一下位运算到底能解决一些什么问题
其实位运算最典型的就运算符号就是,| & ^ 三个,但是运用到具体题目上就很灵活了,基本这个系列也只是复习一下,知道一下如何用二进制的位来存储获取值,而用二进制位这样的数据结构时,位运算就是关联使用的算法了;
其他的,我也不知道啊,就是觉得位运算好酷,有一些特殊的题目,直接用位运算就能几行解决,所以学学可以装个逼,因此这个系列暂时比较少,就两套经典题而已,以后在补充吧;
PS: 其实整理题目至此,已经有 6 组了,最初是为了复习写过的代码,但是越写越觉得自己懂的少,开始疲惫的,但是坚持下去应该会有收获的吧,加油💪
只出现一次的数字 -- 所有题目都是线性时间复杂度,空间复杂度都是常数级复杂度
分析
分析 -- 1个单值,其余是两个
var singleNumber = function(nums) {
return nums.reduce((prev,cur) => prev ^ cur,0) // 0 和任何值异或都等于任何值,所以以 0 为初始值
};
分析 -- 1个单值 x ,其余是 3 个 y1,y2...
/** * @分析 --- 一个值出现 1 次,其余值出现 3次 -- * 1. 将所有值相加,转成二进制,然后相同的值在同一个位上肯定也是一样的,然后对每一个位进行除 3 取余,得到的值就是唯一一个出现 1 次的值了 */
var singleNumber = function (nums) {
let ret = 0;
for (let i = 0; i < 32; i++) {
const temp = 1 << i;
let count = 0;
nums.forEach((num) => {
if (num & temp) count++;
});
// 在 i 这个位上,有 count 这么多个值
if (count % 3) ret |= temp;
}
return ret;
};
只出现一次的数字 -- 所有题目都是线性时间复杂度,空间复杂度都是常数级复杂度
位值
// 260. 只出现一次的数字 III
var singleNumber = function(nums) {
// 求出的 res 是 x1 ^ x2 的异或值
const res = nums.reduce((prev,cur) => prev^ cur,0)
let bite= 0
// 求出 res 在二进制中的第一个 1 的位置,
while((1<<bite) & res === 0 ){
bite++
}
// 这个二进制位对应的值,用它可以求出所有存在这个为的值
// x1,x2 有且仅有一个会与 temp 的 & 运算不为 0
const temp = 1<<bite
let left = 0,right = 0
nums.forEach(num => {
if(num & temp){
left ^= num // 保证 left 是存在 bite 位的值,其他出现两次的值会被异或掉
}else{
right ^= num
}
})
return [left,right]
};
分析 -- 数学法
var subsets = function (nums) {
let ret = [[]] // 默认空数组
for(let num of nums){
ret = [...ret,...ret.map(item => item.concat(num))]
}
return ret
}
分析 -- 迭代+位运算
var subsets = function (nums) {
const ret = []
const len = nums.length
for(let i = 0;i<(1<<len);i++){
// i 就是其中一个二进制话的自己,所以要将它转成数组
const temp = []
for(let j=0;j<len;j++){
if(i & (1<<j)){
// i 中存在下标为 j 的数
temp.push(nums[j])
}
}
// 现在 temp 就是 i 转成数组的子集了
ret.push(temp)
}
return ret
}
分析 -- 迭代
var subsets = function (nums) {
const ret = []
const len = nums.length;
const dfs = (start,arr) => {
if(start === len){
ret.push(arr)
return
}
dfs(start+1,[...arr])
dfs(start+1,[...arr,nums[start]])
}
dfs(0,[])
return ret
}
参考视频:传送门
分析
模拟二叉树迭代法
,这里的核心思想就是带参数的自顶向下的遍历,然后每次遍历分两种状态,一种是取值,一种是不取,而这恰好和组合去重匹配;isGet===false
, 且当前值 numsstart 和上一个值 numsstart-1 相等,那么这一次的遍历有且仅有一个,就是不取值,原因是上一次遍历中的 isGet===true
+ 它后续子树的 isGet===false
分支会与 isGet===false
+ 后续子树的isGet===true
重叠,在这里我们把 isGet===false
+ 后续子树的isGet===true
的分支剪去var subsetsWithDup = function (nums) {
nums.sort((a, b) => a - b); // 排序
const ret = [];
const len = nums.length;
const dfs = (start, arr, isGet) => {
if (start === len) {
ret.push(arr);
return
}
if(!isGet && nums[start] === nums[start-1]){
// 如果当前值和上一次值相同,且这个遍历上一次是没有取值的;那么必定有一个分支是取值了的,如果这里的临时数组取了值,就会和上边那个分支重叠,所以要剪枝
dfs(start+1,[...arr],false)
}else{
dfs(start+1,[...arr],false)
dfs(start+1,[...arr,nums[start]],true)
}
};
dfs(0,[],true) // 初始化是true,这样就可以避开第一次与前一个值进行比较的判定
return ret
};
分析
var findErrorNums = function (nums) {
const res = nums.reduce((prev, cur, index) => prev ^ cur ^ (index + 1), 0);
let temp = 1
// 找出第一个值为 1 的位
while((temp & res) === 0){
temp= temp << 1
}
// 所有存在这个位的的 num 和 index 的数量
let left = 0,right = 0
for(let i = 0;i<nums.length;i++){
if(nums[i] & temp) {
left ^=nums[i]
}
if((i+1) & temp) {
left ^=(i+1)
}
if((nums[i] & temp) === 0) {
right ^=nums[i]
}
if(((i+1) & temp) === 0) {
right ^=(i+1)
}
}
for(let num of nums){
if(left === num){
return [left,right]
}
}
return [right,left]
};
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。