前记
本文整理的前端常见算法,包括多种方法的实现和注意点提示,由于篇幅较长,不对具体实现进行解释。相信自己一时看不懂敲一遍,你自然就懂了。刷算法题一般推荐在leetcode网站,持续更新中,值得收藏!
算法题汇总
-
随机打乱数组顺序
function upsetArr(arr){ return arr.sort(function(){return Math.random()-0.5}); } -
随机获取数组中的元素
function getRadomFromArr(arr){ return arr[Math.floor(Math.random()*arr.length)];//floor向下取整 } -
获取指定范围内的随机整数
function getRadomNum(min,max){ return Math.floor(Math.random() * (max - min + 1)) + min; } -
随机生成指定长度的字符串
function getRandomStr(len){ var str='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ9876543210'; var strLen= str.length;//先求长度存值,节省循环每次重新计算时间 var result=''; for(var i=1;i<=len;i++){ var num = Math.floor(Math.random()*strLen); result+=str[num];//或str.charAt(num) } return result; } -
返回变量类型
function getType(obj){ return Object.prototype.toString.call(obj).slice(8,-1); } //toString.call()返回如[object Array]、[object Object]、[object String] -
不借助临时变量,进行两个整数的交换
function swap(a,b){ a = a-b; b = a+b; a = b-a; return [a,b]; } swap(1,2);//2,1 swap(3,4);//4,3 //另一种实现 // a = a+b; // b = a-b; // a = a-b; -
合并两个数组(5种方法)
//方法一:concat--a,b数组不变,并返回新数组。内存浪费,效率较慢 function merge1(arr1,arr2){ return arr1.concat(arr2); } //方法二:循环--命令式编程写法麻烦 function merge2(arr1,arr2){ for(var i in arr2){ arr1.push(arr2[i]); } return arr1; } //方法三:push结合apply--写法啰嗦难记 function merge3(arr1,arr2){ Array.prototype.push.apply(arr1,arr2); return arr1; } //方法四:push结合es6拓展运算符--对方法三改造,代码优雅 function merge4(arr1,arr2){ arr1.push(...arr2); return arr1; } //方法五:es6拓展运算符 function merge5(arr1,arr2){ return [...arr1,...arr2]; } //总结: //(1)方法一和五不会改变原数组,而方法二三四会改变原数组arr1 //(2)concat浪费内存且效率较慢,因为新建了数组。 -
数组去重(4种方法)
//方法一、 用一个hashtable的结构记录已有的元素,避免内层循环(效率高) function unique1(arr){ var result=[],hash={}; for(var i=0;i<arr.length;i++){ var elem = arr[i]; if(!hash[elem]){ result.push(elem); hash[elem]=true; } } console.log("unique1",result); return result; } var arr1=[1,2,3,4,4,3]; unique1(arr1);//[1,2,3,4] //方法二、利用indexOf查找元素arr[i]在result数组中是否存在 function unique2(arr){ var result=[]; for(var i=0;i<arr.length;i++){ if(result.indexOf(arr[i])==-1){ //如果不重复,加进去 result.push(arr[i]); } } // arr.forEach(item=>{ //forEach遍历更简洁 // if(result.indexOf(item)===-1){ //result.push(item); // } // }) console.log("unique2",result); return result; } var arr2=[1,2,3,4,4,3]; unique2(arr2);//[1,2,3,4] //方法三、从i往下找到重复的元素进行删除(改变原数组) function unique3(arr){ for(var i=0;i<arr.length;i++){ for (var j=i+1;j<arr.length;j++) { if(arr[j]==arr[i]){//从i往下找到重复的元素进行删除 arr.splice(j,1);//从第j个开始删除一个元素 } } } console.log("unique3",arr); } var arr3=[1,2,3,4,4,3]; unique3(arr3);//[1,2,3,4] //方法四、借助ES6的Set数据结构不重复值特性 function unique4(arr){ var newArr = [...new Set(arr)]; console.log("unique4",newArr); return newArr; } var arr4=[1,2,3,4,4,3]; unique4(arr4);//[1,2,3,4] -
数组二分/折半查找(2种方法)
//注意:二分查找必须是有序数组// 方法一、非递归折半查找 function search(arr,num){ let len = arr.length, start = 0, end = len-1; while(start <= end){ var mid = Math.floor((end-start)/2+start);//防溢出 if(num==arr[mid]){ return mid; }else if(num>arr[mid]){ start = mid + 1;// 查找数大于中间数 }else{ end = mid - 1; } } return -1; } // 方法二、递归折半查找 function search2(arr,num,start,end){ if(start>end) return -1;//查找不到 start = start===undefined ?0:start, end = end===undefined ? arr.length-1:end; var mid = Math.floor((end-start)/2+start); if(num==arr[mid]){ return mid; }else if(num>arr[mid]){ return search2(arr,num,mid+1,end); }else{ return search2(arr,num,start,mid-1); } } var arr=[1,2,4,6,8,10,12]; console.log("折半查找下标",search(arr,8));//4 console.log("折半查找下标2",search2(arr,8));//4 -
返回数组中两数相加等于target的所有组合下标
//借用哈希结构 function twoSum(nums,target){ var temp = {}; let result=[]; for(var i=0;i<nums.length;i++){ var dif = target - nums[i]; //该元素与 target 之间的差值 dif if(temp[dif] != undefined){ //找到差值已存在 result.push([temp[dif],i]); } temp[nums[i]] = i;//存储该元素下标到哈希表 } return result; } var arr=[1,9,3,4,8,6]; console.log(twoSum(arr,9));//[[0,4],[2,5]] 因为1+8=9,3+6=9 //拓展:若查找三数相加之和呢?
-
判断一个字符串是否是回文串
function checkStr(str){ return str.split("").reverse().join("") == str; } console.log("abcabc是回文吗?",checkStr("abcabc"));//false console.log("abcba是回文吗?",checkStr("abcba"));//true
-
给定一个字符串,找出其中不含有重复字符的 最长子串 的长度。
function lenOfLongestSubstr(str) { let num = 0,res = 0; let m = ''; for (n of str) { if(str.length-str.indexOf(n) + m.length<=res) break;//提前结束,可不加 if (m.indexOf(n) == -1) { //未重复 m += n;//维护子串 num++; res = res < num ? num: res; } else { m += n; m = m.slice(m.indexOf(n)+1);//重复位置开始截取 num = m.length; } } return res; }; console.log(lenOfLongestSubstr("abcbdefijkl"));//9 因为最长串'cbdefijkl'
-
返回最大子序和。(三种方法--经典)
//描述:给定一个整数数组 nums ,找到一个具有最大和的连续子数组,返回其最大和。 // 方法一:动态规划算法--在每一个扫描点计算以该点数值为结束点的结果 var maxSubArray = function(nums) { var maxSum=nums[0]; var curSum=nums[0]; for(var i=1;i<nums.length;i++) { if(nums[i-1]>0) nums[i]+=nums[i-1];//改变原数组的值为当i个数组的最大和 maxSum = Math.max(maxSum,nums[i]); console.log(i,nums,maxSum); } return maxSum; }; console.log(maxSubArray([-2,1,-3,4,-1,2,1,-5,4]));//6 最大和子数组[4,-1,2,1]
// 方法二:贪心算法--每一步都选择最佳方案,到最后就是全局最优的方案 var maxSubArray2 = function(nums) { var maxSum=nums[0]; var curSum=nums[0]; for(var i=1;i<nums.length;i++){ curSum = Math.max(nums[i], curSum + nums[i]); maxSum = Math.max(maxSum, curSum); console.log(i,curSum,maxSum); } return maxSum; }; console.log(maxSubArray2([-2,1,-3,4,-1,2,1,-5,4]));//6
// 方法三:精妙的分治算法--将问题分解为更小子问题,再逐个解决,再将子问题合并 function crossSum(nums,left,right,p){ if (left == right) return nums[left]; var leftSubsum = Number.MIN_VALUE; var currSum = 0; for(var i = p; i > left - 1; --i) { currSum += nums[i]; leftSubsum = Math.max(leftSubsum, currSum); } var rightSubsum = Number.MIN_VALUE; currSum = 0; for(var i = p + 1; i < right + 1; ++i) { currSum += nums[i]; rightSubsum = Math.max(rightSubsum, currSum); } return leftSubsum + rightSubsum; }; function helper(nums,left,right){ if (left == right) return nums[left]; var p = Math.floor((left + right) / 2);//记得取整 var leftSum = helper(nums, left, p); var rightSum = helper(nums, p + 1, right); var cSum = crossSum(nums, left, right, p); return Math.max(Math.max(leftSum, rightSum), cSum); }; var maxSubArray3 = function(nums) { return helper(nums, 0, nums.length - 1); }; console.log(maxSubArray3([-2,1,-3,4,-1,2,1,-5,4]))//6 //动态规划和贪心算法时间复杂度均为O(N),但分治算法为O(NlogN) //这题集中了五大算法中的动态规划和分治法和贪心算法,另外还有回溯法和分支限界法。
-
正则去掉字符串空格
//去掉全部字符串 str = str.replace(/\s*/g,""); //去掉左边字符串 str = str.replace(/^\s*/g,""); //去掉右边字符串 str = str.replace(/\s*/g,"");
-
斐波那契数列第n项的值实现(2种方法)
//说明:1,1,2,3,5,8,13...这样的数列称为斐波那契数列:fn(n)=fn(n-1)+fn(n-2) //方法一:递归实现 function fn(n){ if(n==1||n==2) return 1; return func(n-1)+func(n+1); } fn(6);//8 //方法二:非递归实现 function fn(n){ if(n==1||n==2) return 1; let a=1,b=1,res=0;//类似a=fn(n-2);b=fn(n-1) for(let i=3;i<=n;i++){ res=a+b; a=b; b=res; } return res; } fn(6);//8
-
函数柯里化(currying)
//经典面试题:实现一个add方法,使计算结果能够满足如下预期: // add(1)(2)(3) = 6; // add(1, 2, 3)(4) = 10; // add(1)(2)(3)(4)(5) = 15;
function add() { // 第一次执行时,定义一个数组专门用来存储所有的参数 var _args = Array.prototype.slice.call(arguments);//类似组转为数组 // var _args = Array.from(arguments);//Array.from同样可将类数组转为数组 // 在内部声明一个函数,利用闭包的特性保存_args并收集所有的参数值 var _adder = function() { _args.push(...arguments); return _adder; }; // 利用toString隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回 _adder.toString = function () { return _args.reduce(function (a, b) { return a + b; }); } return _adder; }
-
统计下边数组中total的总和
var arr =[ {id: 1,type: 'A',total: 3}, {id: 2,type: 'B',total: 4}, {id: 3,type: 'C',total: 5}, ]; //方法一:普通实现-命令式编程 function sum1(arr) { let sum = 0; for (let i = 0; i < arr.length; i++) { sum += arr[i].total; } return sum; } //方法二:reduce(聚合)实现-函数式编程 function sum2(arr){ return arr.reduce((sum, { total }) => { return sum + total; }, 0) }
-
实现对象深浅拷贝
//浅拷贝:只能解决对象第一层的拷贝,当对象有多层时就需要使用深拷贝 //深拷贝:将对象及值复制过来,两个对象修改其中任意的值另一个值不会改变
// 1) 浅拷贝 let a = {age: 20}; let b = Object.assign({}, a);//方法一:使用assign浅拷贝 let c = {...a};//方法二:使用展开运算符浅拷贝 a.age = 30; console.log(b.age,c.age) // 20 20
// 2) 深拷贝 function deepClone(target) { // 实现深度拷贝的函数 function _checkType(data){ return Object.prototype.toString.call(data).slice(8, -1);//类型 } let result; let type = _checkType(target); if (type === 'Object') { result = {}; // 只有对象和数组才进行深度拷贝 } else if (type === 'Array') { result = [] } else { return target } //也可以改为三元表达式写法更简洁 // result = type === 'Object'?{}:(type === 'Array'?[]:target); for (let i in target) { // i为对象的key或数组的下标 let value = target[i] let valueType = _checkType(value) if (valueType === 'Object' || valueType === 'Array') { // 当拷贝的对象中还有数组或是对象时进行递归 result[i] = deepClone(value)
} else { result[i] = value } } return result // 返回最终的结果 } let person = {age: 20,name:{firstName:'jamie'}}; deepClone(person); -
自定义事件
var content = document.querySelector('.content'); // 自定义事件 var evt = new Event('custom'); var customEvt = new CustomEvent('customEvt', { // 通过这个属性传递参数 detail: { name: 'tom', age: 12 } }); content.addEventListener('custom', (e) => { console.log('自定义事件被触发,无参数...'); console.log(e); }); content.addEventListener('customEvt', (e) => { console.log('自定义事件被触发,有参数...'); console.log(e); console.log(e.detail); }); // 点击时触发这个自定义事件 content.addEventListener('click', (e) => { content.dispatchEvent(evt); content.dispatchEvent(customEvt); });
-
最长递增子序列
//例如[0, 3, 4, 17, 2, 8, 6, 10] 的最长递增子序列[0, 3, 4, 8, 10] //动态规划实现 function longestList(n) { if (n.length === 0) return 0 // 创建一个和参数相同大小的数组,并填充值为 1 let array = new Array(n.length).fill(1) // 从索引 1 开始遍历,因为数组已经所有都填充为 1 了 for (let i = 1; i < n.length; i++) { // 从索引 0 遍历到 i // 判断索引 i 上的值是否大于之前的值 for (let j = 0; j < i; j++) { if (n[i] > n[j]) { array[i] = Math.max(array[i], 1 + array[j]) } } } let res = 1 for (let i = 0; i < array.length; i++) { res = Math.max(res, array[i]) } return res }