前端面试常见算法题整理(收藏)

1,960 阅读3分钟

前记

本文整理的前端常见算法,包括多种方法的实现和注意点提示,由于篇幅较长,不对具体实现进行解释。相信自己一时看不懂敲一遍,你自然就懂了。刷算法题一般推荐在leetcode网站,持续更新中,值得收藏!

算法题汇总

  1. 随机打乱数组顺序

    function upsetArr(arr){
      return arr.sort(function(){return Math.random()-0.5});
    }
    
  2. 随机获取数组中的元素

    function getRadomFromArr(arr){
      return arr[Math.floor(Math.random()*arr.length)];//floor向下取整
    }
    
  3. 获取指定范围内的随机整数

    function getRadomNum(min,max){
     return Math.floor(Math.random() * (max - min + 1)) + min;
    }
    
  4. 随机生成指定长度的字符串

    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;
    }
    
  5. 返回变量类型

    function getType(obj){
      return Object.prototype.toString.call(obj).slice(8,-1);
    }
    //toString.call()返回如[object Array]、[object Object]、[object String]
    
  6. 不借助临时变量,进行两个整数的交换

    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;
    
  7. 合并两个数组(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浪费内存且效率较慢,因为新建了数组。
    
  8. 数组去重(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]
    
  9. 数组二分/折半查找(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
    
  10. 返回数组中两数相加等于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 //拓展:若查找三数相加之和呢?

  11. 判断一个字符串是否是回文串

    function checkStr(str){ return str.split("").reverse().join("") == str; } console.log("abcabc是回文吗?",checkStr("abcabc"));//false console.log("abcba是回文吗?",checkStr("abcba"));//true

  12. 给定一个字符串,找出其中不含有重复字符的 最长子串 的长度。

    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'

  13. 返回最大子序和。(三种方法--经典)

    //描述:给定一个整数数组 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) //这题集中了五大算法中的动态规划和分治法和贪心算法,另外还有回溯法和分支限界法。

  14. 正则去掉字符串空格

    //去掉全部字符串 str = str.replace(/\s*/g,""); //去掉左边字符串 str = str.replace(/^\s*/g,""); //去掉右边字符串 str = str.replace(/\s*/g,"");//去掉两头空格str=str.replace(/\s\s/g,""); //去掉两头空格 str = str.replace(/^\s*|\s*/g,"");

  15. 斐波那契数列第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

  16. 函数柯里化(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; }

  17. 统计下边数组中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) }

  18. 实现对象深浅拷贝

    //浅拷贝:只能解决对象第一层的拷贝,当对象有多层时就需要使用深拷贝 //深拷贝:将对象及值复制过来,两个对象修改其中任意的值另一个值不会改变

    // 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);

  19. 自定义事件

    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); });

  20. 最长递增子序列

    //例如[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 }