11

【灵活应对前端面试中的JS算法题】

  • 实现一个函数clone,可以对JavaScript中的5种主要的数据类型(包括Number、String、Object、Array、Boolean)进行值复制
    function clone(obj){
        var result;
        switch(typeof obj){
            case 'undefined':
            break;
            case 'string':
            result = obj+'';
            break;
            case 'number':
            result = obj-0;
            break;
            case 'boolean':
            result =obj;
            break;
            case 'object':
                if(obj ===null){
                    result = null;
                } else {
                    if(Object.prototype.toString.call(obj).slice(8,-1)==='Array'){
                        result=[];
                        for(var i=0;i<obj.length;i++){
                            result.push(clone(obj[i]));
                        }
                    }else{
                        result=[];
                        for(var k in obj){
                            result[k]=clone(obj[k]);
                            }
                        }
                };
                break;
             default:
                 result = obj;
                 break;
            
        }
        return result
    }
  • 判断一个单词是否是回文

回文是指把相同的词汇或句子,在下文中调换位置或颠倒过来,产生首尾回环的情趣,叫做回文,也叫回环。

很多人拿到这样的题目非常容易想到用for将字符串颠倒字母顺序然后匹配就行了。其实重要的考察的是对于reverse的实现。其实我们可以利用现成的函数,将字符串转换成数组,这个思路很重要,我们可以拥有更多的自由度去进行字符串的一些操作。
let reverseStr = function(str) {  
    return str = str.split('').reverse().join('');
}
reverseStr('abcdefg');
//gfedcba
  • 在句子中反转词

如fix this one 变为 one this fix。重点是检测到空格时进行处理。

    function reverseWord(str){
        return str.split(' ').reverse().join(' ')
    }
  • 反转每个单词中字符的顺序

“I am the good boy” 反转成这样 “I ma eht doog yob”

    function reverse(str){
        return str.split(' ').reverse().join(' ').split('').reverse().join('');
    }
  • 去掉一组整型数组重复的值

    题目如下输入: [3,13,24,11,11,14,1,2]
    输出: [3,13,24,11,14,2]
    需要去掉重复的11 和 1 这两个元素。

这道题有多重方法,我理解的主要是考察个人对Object的使用,利用key来进行筛选。
    function unique(arr) {  
        let hashTable = {};
        let data = [];
        for(let i=0,l=arr.length;i<l;i++) {
           if(!hashTable[arr[i]]) {
             hashTable[arr[i]] = true;
             data.push(arr[i]);
            }
         }
         return data
    }
    unique([3,13,24,11,11,14,1,2])
    //[3,13,24,11,14,2]

再来一个其他实现方式,这个方法常在我的项目中出现,用的时候确实觉得代码少了那么几行

     function unique(arr) {  
        let data = [];
        for(let i=0,l=arr.length;i<l;i++) {
           if(data.indexOf(arr[i])==-1) {
             data.push(arr[i]);
            }
         }
         return data
    }
    unique([3,13,24,11,11,14,1,2])
    //[3,13,24,11,14,2]
  • 统计一个字符串出现最多的字母

输入一段英文连续的英文字符串 afjghdfraaaasdenas,找出重复出现次数最多的字母

    function findMaxChar(str) {  
          if(str.length == 1) {
            return str;
          }
          let charObj = {};
          for(let i=0;i<str.length;i++) {
            if(!charObj[str.charAt(i)]) {
              charObj[str.charAt(i)] = 1;
            }else{
              charObj[str.charAt(i)] += 1;
            }
          }
          return compare(charObj);
     };
     function compare(charObj){
         let maxChar = '',
         maxValue = 1;
         for(var k in charObj) {
            if(charObj[k] >= maxValue) {
              maxChar = k;
              maxValue = charObj[k];
            }
         }
         return maxChar;
     }
     findMaxChar('afjghdfraaaasdenas')
    //a
  • 找到字符串中的第一个非重复的字符

遍历字符串,用一个对象当做hash表来存储重复字符。

    function firstNonRepeatChar(str){
        var count = {};
        for(var i=0;i<str.length;i++){
            if(count[str[i]]){
                count[str[i]]++;
            }else{
                count[str[i]] = 1;
            }   
        }
    
        for(var prop in count){
            if(count[prop] === 1){
                return prop;
            }
        }
    
    }
  • 删除字符串中重复的字符

其实是在上一个问题的基础上再进行操作:

    function firstNonRepeatChar(str){
        var count = {};
        var result = [];
        for(var i=0;i<str.length;i++){
            if(count[str[i]]){
                count[str[i]]++;
            }else{
                count[str[i]] = 1;
            }   
        }
    
        for(var prop in count){
            if(count[prop] === 1){
                result.push(prop);
            }
        }
    
        return result.join('');
    }
  • 在n和m之间生成随机整数
    Math.floor(Math.random()*(m-n))+n
  • 排序算法(冒泡排序)

冒泡排序JavaScript代码实现:

    function bubbleSort(arr) {
        var len = arr.length;
        for (var i = 0; i < len; i++) {
            for (var j = 0; j < len - 1 - i; j++) {
                if (arr[j] > arr[j+1]) {        //相邻元素两两对比
                    var temp = arr[j+1];        //元素交换
                    arr[j+1] = arr[j];
                    arr[j] = temp;
                }
            }
        }
        return arr;
    }
    bubbleSort([3,5,2,8,9,7,6])
    //[2, 3, 5, 6, 7, 8, 9]
  • 排序算法(选择排序)
在时间复杂度上表现最稳定的排序算法之一,因为无论什么数据进去都是O(n²)的时间复杂度。。。所以用到它的时候,数据规模越小越好。唯一的好处可能就是不占用额外的内存空间了吧。
    function selectionSort(arr) {
        var len = arr.length;
        var minIndex, temp;
        for (var i = 0; i < len - 1; i++) {
            minIndex = i;
            for (var j = i + 1; j < len; j++) {
                if (arr[j] < arr[minIndex]) {     //寻找最小的数
                    minIndex = j;                 //将最小数的索引保存
                }
            }
            temp = arr[i];
            arr[i] = arr[minIndex];
            arr[minIndex] = temp;
        }
        return arr;
    }
     selectionSort([3,5,2,8,9,7,6])
    //[2, 3, 5, 6, 7, 8, 9]
  • 从未排序的整数数组中找出缺失的数字

比如你有1到100的整数,而其中缺了一个,怎么找出这个数字?利用等差数列公式计算这些数应得的和,再计算当前数组所有数字的和,二者的差即为缺少的数。

    function missingNumber(arr){
        var n = arr.length+1;
        var expectedSum = (1+n)*n/2;
        var sum = 0;
        for(var i=0;i<arr.length;i++){
            sum+=arr[i];
        }    
        return expectedSum - sum;
    }
  • 检查是否有任何两个数字的和是给定的数字

最暴力的方法就是两层循环,是O(n2).改进方法使用一个对象作为哈希表,用于存储数,这样在每次搜寻是否有另一个数与当前数的和为sum时就可以在O(1)的时间内找到。

    function twoSum(arr,sum){
        var obj = {};
        var num;
        for(var i=0;i<arr.length;i++){
            num = sum - arr[i];
            if(obj[num]){
                return true;
            }else{
            //若没有的话为当前数字建立索引
                obj[arr[i]] = true;
            }
        }
        return false;
    }
  • 检查是否有任何两个数字的和是给定的数字,有的话将数字和位置以对象的方式返回值,否则返回false

最暴力的方法就是两层循环,是O(n2).改进方法使用一个对象作为哈希表,用于存储数,这样在每次搜寻是否有另一个数与当前数的和为sum时就可以在O(1)的时间内找到。

    function twoSum(arr,sum){
        var obj = {};
        var num;
        for(var i=0;i<arr.length;i++){
            num = sum - arr[i];
            obj[num]=arr.length+1;
            if(obj[num]){
                obj[arr[i]]=i;
                obj[num]=arr.indexOf(num);
                return obj;
            }else{
            //若没有的话为当前数字建立索引
                obj[arr[i]] = i;
            }
        }
        return false;
    }
  • 找到任意两个数字的最大和

找到两个最大的数并返回它们的和。

    function topSum(arr){
        if(arr.length<2) return null;
        var first,second;
        if(arr[0]>arr[1]){
            first = arr[0];
            second=arr[1];
        }else{
            first = arr[1];
            second=arr[0];
        }
    
        for(var i=2;i<arr.length;i++){
            if(arr[i]>first){
                second = first;
                first = arr[i];
            }else if(arr[i]>second){
                second = arr[i];
            }
        }
    
        return first+second;
    }
  • 从1到n中0的总个数
n=50的话,有5个0,分别是10,20,30,40,50。
n = 120的话,分别是10到90,共九个,110到120,共2个,以及100的两个,一共13个。
也就是说10的整数次方会有多个零,如100,1000,那么就要利用现有的数计算包含了多少个10的平方数。
如2014,2014/10=201; 201/10 = 20; 20/10 = 2; 最后表明出现了两次10的三次方,即1000和2000。
    function countZero(n){
        var count = 0;
        while(n>0){
            count+=Math.floor(n/10);
            n/=10;
        }
        return count;
    }
  • 匹配字符串的子字符串
    function substr(str,subStr){
        for(var i=0;i<str.length;i++){
            for(var j=0;j<subStr.length;j++){
                if(str[i+j] != subStr[j]){
                    break;
                }
            }
            if(j == subStr.length){
                return i;
            }
        }
        return -1;
    
    }
  • 字符串的全排列
    var result = [];
    function permutations(str){
        var arr= str.split('');
    
        helper(arr,0,[]);
        return result;
    }
    function helper(arr,index,list){
        if(index === arr.length){
            result.push(list.join(''));
            return;
        }
        for(var i = 0;i<arr.length;i++){
            if(list.indexOf(arr[i]) != -1){
                continue;
            }
            list.push(arr[i]);
            helper(arr,index+1,list)
            list.pop();
        }
    }
  • 不借助临时变量,进行两个整数的交换
把 a = 2, b = 4 变成 a = 4, b =2
这种问题非常巧妙,需要大家跳出惯有的思维,利用 a , b进行置换
主要是利用 + – 去进行运算,类似 a = a + ( b – a) 实际上等同于最后 的 a = b;
    function swap(a , b) {  
      b = b - a;
      a = a + b;
      b = a - b;
      console.log('a='+a);
      console.log('b='+b)
    }
    var a=2,b=4;
    swap(a,b)
    //a=4;b=2
  • 斐波那契数列(黄金分割数列)不说换金分割我也不知道是啥玩意儿啦

斐波那契数列,又称黄金分割数列,指的是这样一个数列:0、1、1、2、3、5、8、13、21、34、……在数学上,斐波纳契数列主要考察递归的调用。

    function getFibonacci(n) {  
      var fibarr = [];
      var i = 0;
      while(i<n) {
        if(i<=1) {
          fibarr.push(i);
        }else{
          fibarr.push(fibarr[i-1] + fibarr[i-2])
        }
        i++;
      }
      return fibarr;
    }
    getFibonacci(9); //拿到9个
    //[0、1、1、2、3、5、8、13、21]
  • 获得第n个斐波那契数字

解法一:迭代

var fibonacci = function(n){
    var fibo = [0,1];
    for(var i=2;i<=n;i++){
        fibo[i] = fibo[i-1]+fibo[i-2];
    }
    return fibo[n];
}

解法二:递归

    var fibonacci = function(n){
        if(n>=2){
            return fibonacci(n-1)+fibonacci(n-2);
        }else{
            return n;
        }
    }
  • 找到两个数的最大公约数

解法一:遍历

    var greatestCommonDivisor= function(a,b){
        if(a<2 || b<2) return 1;
        var divider = 2;
        var greatestDivisor = 1;
        while(divider<=a && divider<=b){
            if(a%divider == 0 && b%divider == 0){
                greatestDivisor = divider;
            }
            divider++;
        }
        return greatestDivisor;
    }

解法二:辗转相除法
又名欧几里德算法(Euclidean algorithm)乃求两个正整数之最大公因子的算法。它是已知最古老的算法……
有解释的,但我选择不去理解……

    function greatestCommonDivisor(a, b){
       if(b == 0)
         return a;
       else 
         return greatestCommonDivisor(b, a%b);
    }
  • 合并两个排序数组
var mergeSortedArray = function(a,b){
    var merge = [];
    var i = 0,j = 0;
    var k = 0;
    while(i<a.length || j<b.length){
        if(i == a.length || (j!=b.length && a[i]>b[j])){
            merge[k++] = b[j++];
        }else{
            merge[k++] = a[i++];
        }
    }
    return merge; 
}
  • 验证一个数是否是质数

质数只能被1和它自己整除,因此令被除数从2开始,若能整除则不是质数,若不能整除则加一,直到被除数到达根号n,此时n则是质数。

    function isPrime(n){
        var divider = 2;
        var limit = Math.sqrt(n);
        while(divider<=limit){
            if(n%divider == 0){
                return false;
            }
            divider++;
        }
        return true;
    }
    isPrime(3);
    //true
  • 查找数字的所有质数因子

divider从2开始,如果n能整除divider,则将divider加入到结果中,n为此次计算后的商,如果n不能整除divider,则divider++

var primeFactors = function(n){
    var factors = [];
    var divider = 2;
    while(n>2){
        if(n%divider == 0){
            factors.push(divider);
            n /= divider;
        }else{
            divider++;
        }
    }
    return factors;
}
  • 找出正数组的最大差值比

这是通过一道题目去测试对于基本的数组的最大值和最小值的查找

     function getMaxProfit(arr) {
        var minPrice = arr[0];
        var maxProfit = 0;
        for (var i = 0; i < arr.length; i++) {
            var currentPrice = arr[i];
            minPrice = Math.min(minPrice, currentPrice);
            var potentialProfit = currentPrice - minPrice;
            maxProfit = Math.max(maxProfit, potentialProfit);
        }
        return maxProfit;
    }
    getMaxProfit([10,5,11,7,8,9])
    //6
  • 随机生成指定长度的字符串
    function randomString(n) {  
      let str = 'abcdefghijklmnopqrstuvwxyz9876543210';
      let tmp = '',
          i = 0,
          l = str.length;
      for (i = 0; i < n; i++) {
        tmp += str.charAt(Math.floor(Math.random() * l));
      }
      return tmp;
    }
    randomString(9);  //指定长度为9
    //4ldkfg9j7
  • 实现类似getElementsByClassName 的功能

自己实现一个函数,查找某个DOM节点下面的包含某个class的所有DOM节点?不允许使用原生提供的 getElementsByClassName querySelectorAll 等原生提供DOM查找函数。

    function queryClassName(node, name) {  
      var starts = '(^|[ \n\r\t\f])',
           ends = '([ \n\r\t\f]|$)';
      var array = [],
            regex = new RegExp(starts + name + ends),
            elements = node.getElementsByTagName("*"),
            length = elements.length,
            i = 0,
            element;
     
        while (i < length) {
            element = elements[i];
            if (regex.test(element.className)) {
                array.push(element);
            }
     
            i += 1;
        }
     
        return array;
    }
    queryClassName()
  • 使用JS 实现二叉查找树

一般叫全部写完的概率比较少,但是重点考察你对它的理解和一些基本特点的实现。 二叉查找树,也称二叉搜索树、有序二叉树(英语:ordered binary tree)是指一棵空树或者具有下列性质的二叉树:

任意节点的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
任意节点的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 任意节点的左、右子树也分别为二叉查找树;
没有键值相等的节点。二叉查找树相比于其他数据结构的优势在于查找、插入的时间复杂度较低。为O(log
n)。二叉查找树是基础性数据结构,用于构建更为抽象的数据结构,如集合、multiset、关联数组等。
    class Node {  
      constructor(data, left, right) {
        this.data = data;
        this.left = left;
        this.right = right;
      }
    }
    
    class BinarySearchTree {
      constructor() {
        this.root = null;
      }
      insert(data) {
        let n = new Node(data, null, null);
        if (!this.root) {
          return this.root = n;
        }
        let currentNode = this.root;
        let parent = null;
        while (1) {
          parent = currentNode;
          if (data < currentNode.data) {
            currentNode = currentNode.left;
            if (currentNode === null) {
              parent.left = n;
              break;
            }
          } else {
            currentNode = currentNode.right;
            if (currentNode === null) {
              parent.right = n;
              break;
            }
          }
        }
      }
     
      remove(data) {
        this.root = this.removeNode(this.root, data)
      }
     
      removeNode(node, data) {
        if (node == null) {
          return null;
        }
        if (data == node.data) {
          // no children node
          if (node.left == null && node.right == null) {
            return null;
          }
          if (node.left == null) {
            return node.right;
          }
          if (node.right == null) {
            return node.left;
          }
          let getSmallest = function(node) {
            if(node.left === null && node.right == null) {
              return node;
            }
            if(node.left != null) {
              return node.left;
            }
            if(node.right !== null) {
              return getSmallest(node.right);
            }
          }
          let temNode = getSmallest(node.right);
          node.data = temNode.data;
          node.right = this.removeNode(temNode.right,temNode.data);
          return node;
        } else if (data < node.data) {
          node.left = this.removeNode(node.left,data);
          return node;
        } else {
          node.right = this.removeNode(node.right,data);
          return node;
        }
      }
     
      find(data) {
        var current = this.root;
        while (current != null) {
          if (data == current.data) {
            break;
          }
          if (data < current.data) {
            current = current.left;
          } else {
            current = current.right
          }
        }
        return current.data;
      }
     
    }
     
    module.exports = BinarySearchTree;
  • 统计数组中每个元素及出现的次数,并输出到页面
  function getArrayMess(arr) {  
          if(arr.length == 1) {
            console.log("{"+arr[0]+":1")
          }
          let charObj = {};
          for(let i=0;i<arr.length;i++) {
            if(!charObj[arr[i]]) {
              charObj[arr[i]] = 1;
            }else{
              charObj[arr[i]] += 1;
            }
          }
            console.log(charObj)
}
     
  

透明技术人
200 声望3 粉丝