前端面试常见算法

1.数组去重

    //数组去重
    let arr = [1, 3, 7, 5, 4, 5, 2, 1, 3];
    // 方法一:Set数据结构,它类似于数组,其成员的值都是唯一的
    function unique(arr) {
    
    
       //...arr 1 1 3 7 5 4 5 2 1 3
      return [...new Set(arr)]; //...作用是遍历当前使用的对象能够访问到的所有属性,并将属性放入当前对象中
      //或return Array.from(new Set(arr)) //Array.from将一个类数组对象或者可遍历对象转换成一个真正的数组
    }
    console.log(unique([1, 3, 7, 5, 4, 5, 2, 1, 3]));
    //方法二: 利用数组原型对象上的includes方法:判断一个数组是否包含一个指定的值,如果是返回 true,否则false。
    function unique(arr) {
    
    
      var newArr = []
      for (var i = 0; i < arr.length; i++) {
    
    
        if (!newArr.includes(arr[i])) {
    
    
          newArr.push(arr[i])
        }
      }
      return newArr
    }
    console.log(unique([1, 3, 7, 5, 4, 5, 2, 1, 3]));
    ///方法三: 利用数组的indexOf(要查找的value)下标属性来查询:从数组开头向后开始查找目标数所在的位置然后返回它的索引值。未查找到返回-1   
    function unique4(arr) {
    
    
      var newArr = []
      for (var i = 0; i < arr.length; i++) {
    
    
        if (newArr.indexOf(arr[i]) === -1) {
    
    
          newArr.push(arr[i])
        }
      }
      return newArr
    }
    console.log(unique([1, 3, 7, 5, 4, 5, 2, 1, 3]));

2.数组排序

o(1), o(n), o(logn), o(nlogn)基本介绍
这是算法的时空复杂度的表示。不仅仅用于表示时间复杂度,也用于表示空间复杂度。
O后面的括号中有一个函数,指明某个算法的耗时/耗空间与数据增长量之间的关系。其中的n代表输入数据的量。

在这里插入图片描述

O(1)<O(logn)<O(n)<O(nlogn)<O(n^2)<O(2n)<O(n!)

在这里插入图片描述

2.1冒泡排序

冒泡排序是一种简单的排序算法。假设长度为n的数组arr,要按照从小到大排序。首先从数组的第一个元素开始到数组最后一个元素为止,对数组中相邻的两个元素进行比较,如果位于数组左端的元素大于数组右端的元素,则交换这两个元素在数组中的位置,此时数组最右端的元素即为该数组中所有元素的最大值。接着对该数组剩下的n-1个元素进行冒泡排序,直到整个数组有序排列。算法的时间复杂度为O(n^2)。

代码实现:
步骤1: 比较相邻的元素。如果第一个比第二个大,就交换它们两个;
步骤2: 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数;
步骤3: 针对所有的元素重复以上的步骤,除了最后一个;
步骤4: 重复步骤1~3,直到排序完成。

在这里插入图片描述

    function unique(arr) {
    
    
      for (var i = 0; i < arr.length - 1; i++) {
    
    
        for (var j = 0; j < arr.length - i - 1; j++) {
    
    
          if (arr[j] > arr[j + 1]) {
    
    
            var temp = arr[j];
            arr[j] = arr[j + 1];
            arr[j + 1] = temp;
          }
        }
      }
      return arr; //一定不要忘记return
    }
    console.log(unique([3, 4, 5, 1, 2]));//[1,2,3,4,5]

2.2选择排序

选择排序:在一个长度为N的无序数组中,在第一趟遍历N个数据,找出其中最小的数值与第一个元素交换,第二趟遍历剩下的N-1个数据,找出其中最小的数值与第二个元素交换……第N-1趟遍历剩下的2个数据,找出其中最小的数值与第N-1个元素交换,至此选择排序完成。

     function fn(arr){
    
    
      for(var i=0;i<arr.length-1;i++){
    
    
      //先把第一个值当做最小值
          var min=i;
          for(var j=i+1;j<arr.length;j++){
    
    
              if(arr[j]<arr[min]){
    
    
                  min = j;
              }
          }
          //数据交换
          var temp=arr[i];
          arr[i]=arr[min];
          arr[min]=temp;
      }
      return arr;
  }
 console.log(fn([6,1,2,4,3,5])) 

2.3插入排序

插入排序:每一步将一个待排序的数据插入到前面已经排好序的有序序列中,直到插完所有元素为止。

插入排序的基本思想就是将无序序列插入到有序序列中。例如要将数组arr=[4,2,8,0,5,1]排序,可以将4看做是一个有序序列(图中用蓝色标出),将[2,8,0,5,1]看做一个无序序列。无序序列中2比4小,于是将2插入到4的左边,此时有序序列变成了[2,4],无序序列变成了[8,0,5,1]。无序序列中8比4大,于是将8插入到4的右边,有序序列变成了[2,4,8],无序序列变成了[0,5,1]。以此类推,最终数组按照从小到大排序。该算法的时间复杂度为O(n^2)。
在这里插入图片描述
总结:不断将大于当前考察对象的数后移一位,知道找到考察对象应该处于的位置。有一个需要注意的点是,考察对象不一定是要一直向前移动到数组的第一个位置才结束一趟排序

    function unique(arr) {
    
    
      // 外层循环:第一个数是有序的,所以从第二个数开始,然后向前面局部有序地插入
      for (var i = 1; i < arr.length; i++) {
    
    
        // 内层循环:获取i位置的元素,和前面的元素进行比较
        var temp = arr[i]
        var j = i
        while (arr[j - 1] > temp && j > 0) {
    
    
          //将大于考察对象的数后移一位
          arr[j] = arr[j - 1]
          //继续比较
          j--
        }
        // 找到考察的数应处于的位置
        arr[j] = temp
      }
      return arr;
    }

2.4快速排序

快速排序的思想很简单,整个排序过程只需要三步:

(1)在数据集之中,选择一个元素作为"基准"(pivot)()。

(2)所有小于"基准"的元素,都移到"基准"的左边;所有大于"基准"的元素,都移到"基准"的右边。

(3)对"基准"左边和右边的两个子集,不断重复第一步和第二步,直到所有子集只剩下一个元素为止。

执行示意图:

在这里插入图片描述

function quick(arr) {
    
    
  if (arr.length <= 1) {
    
    
    return arr;
  }
  var left = [],
    right = [],
    //splice(开始位置, 删除个数, 替换的值): 一般用于删除数组中的元素,返回删除元素组成的数组,改变原数组
    //arr.splice(0, 1)[0];取arr数组中的第一个值
    middle = arr.splice(0, 1)[0];//一般取第一个值作为middle
  for (var i = 0; i < arr.length; i++) {
    
    
    if (arr[i] > middle) {
    
    
      right.push(arr[i]);
    } else {
    
    
      left.push(arr[i]);
    }
  }
//递归方式(下面调用quick)让左右两边的数组持续这样处理,一直到左右两边都排序好为止(左边+中间+右边拼接成最后的结果)左右两边的数组也需执行这个思路
  return quick(left).concat([middle], quick(right));
}

    console.log(quick([13, 28, 37, 49, 49, 65, 76, 97]))

2.5桶排序

桶排序(Bucket sort)核心思想:创建空数组作为桶,将待排序数组遍历到的值作为桶的下标插入

function fn(arr) {
    
    
//创建一个空桶
  var newArr = [];
  //遍历数组中的数,将数值作为桶索引添加数据
  for (var i = 0; i < arr.length; i++) {
    
    
    //arr[i]是需要排列的数字 需要随意填一个数据,这个数据是什么不重要
    newArr[arr[i]] = 1;//新数组的索引为arr[0]-arr[i] 值全为1
  }
  //清空原数组取出下标
  arr.length = 0;
  //用for in 遍历桶对对象的key值进行循环,遍历出来的就是原数组的排好序的数值,空值不会被遍历
  for (var attr in newArr) {
    
    
    arr.push(Number(attr));
  }
  return arr;
}
console.log(fn([13, 28, 37, 49, 49, 65, 76, 97]));

2.6堆排序

堆排序基本介绍
1.堆排序是利用堆这种数据结构而设计的一种排序算法,堆排序是一种选择排序,它的最坏,最好,平均时间复杂度均为O(nlogn),它也是不稳定排序。
2.堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆, 注意 : 没有要求结点的左孩子的值和右孩子的值的大小关系。
3.每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。

堆排序的基本思想是
1.将待排序序列构造成一个大顶堆
2.此时,整个序列的最大值就是堆顶的根节点。
3.将其与末尾元素进行交换,此时末尾就为最大值。
4.然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了。可以看到在构建大顶堆的过程中,元素的个数逐渐减少,最后就得到一个有序序列了.

//将一个数组(二叉树),调整成一个大顶堆
function adjustHeap(arr, i, length) {
    
    
    let temp = arr[i];//先取出当前元素的值,保存在临时变量
    //开始调整
    //说明 调整非叶子节点的顺序时从下到上,从左到右
    //从最下层开始,逐层将大的值往上提
    //1.k=i*2+1是i节点的左子节点
    for (let k = i * 2 + 1; k < length; k = k * 2 + 1) {
    
    
        if (k + 1 < length && arr[k] < arr[k + 1]) {
    
    
            //说明左子节点的值小于右子节点的值
            k++//k指向右子节点
        }
        if (arr[k] > temp) {
    
    
            //如果子节点大于父节点
            arr[i] = arr[k]//把较大的值赋给当前节点
//i指向k,继续循环比较,使的以当前i顶点的二叉树满足大顶堆的条件
//k为i节点的左子节点或右子节点,因为从下往上调整的,
//我们可以认为左子节点树或右子节点树已经是一个大顶堆,
//当这个大顶堆的顶点被某值X替换后,大顶堆被破坏结构,
//此时我们需要从原来的大顶堆右边节点树找到一个位置,将X放入该位置,从而重新形成大顶堆结构
//其实是从把最右边的一排节点逐层上移,X被安放在最右边的一排节点中合适的位置
            i = k;
        } else {
    
    
            break;//调整非叶子节点的顺序时从下到上,从左到右,所以可以中断
        }
        //当for选好结束后,我们已经将以i为父节点的树的最大值,放在了最顶上(局部)
        arr[i] = temp;//将temp值放到调整后的位置
    }
}
let arr = [4, 6, 8, 5, 9, -1, 17, 5, 23, 11, 6];
heapSort(arr);
console.log(arr)

//编写一个堆排序
function heapSort(arr) {
    
    
 //1.将无序序列构成一个堆,根据升序降序需求选择大顶堆或小顶堆
//最后一个值的序号为arr.length-1,根据顺序存储二叉树,
//n节点的左子树为2*n+1,右子树为2*n+2,
//那么最后一个非叶子节点的值应该为Math.floor((arr.length-1-1)/2) 
//= Math.floor((arr.length)/2 -1)
//=Math.floor(arr.length / 2) - 1
    for (let i = Math.floor(arr.length / 2) - 1; i >= 0; i--) {
    
    
        adjustHeap(arr, i, arr.length)
    }
    //2.将堆顶元素与末尾元素交换,将最大元素“沉”到数组末端
    //3.重新调整结构,使其满足堆条件,然后继续交换堆顶元素和当前末尾元素,
    //  反复执行调整+交换步骤,直到整个序列有序
    for (let j = arr.length - 1; j > 0; j--) {
    
    
        //交换
       let temp = arr[j];
        arr[j] = arr[0];
        arr[0] = temp;
        adjustHeap(arr, 0, j);
    }
}

2.7归并排序

归并排序, “归并”的意思是将两个或两个以上的有序表组合成一个新的有序表。假如初始序列含有n个记录,则可看成是n个有序的子序列,每个子序列的长度为1,然后两两归并,得到[n/2](向上取整)个长度为2或1的有序子序列;再两两归并,……,如此重复,直到得到一个长度为n的有序序列为止,这种排序方法称为2-路归并排序。
步骤解析:

1、把长度为n的输入序列分成两个长度为n/2的子序列;

2、对这两个子序列继续分为m/2的子序列,一直分下去,直为1个元素;

3、将两个排序好的子序列合并成一个最终的排序序列。
特点: 速度仅次于快速排序,为稳定排序算法,一般用于总体无序,但是各子项相对有序的数列,属于分治思想,递归归并。

算法演示: 动画来源于https://www.jianshu.com/u/c6ad3f2ed2d6

在这里插入图片描述

//归并排序
function mergeSort(arr){
    
    
  var len = arr.length;
  if(len < 2){
    
    
    return arr;
  }
  //首先将无序数组划分为两个数组
  var mid = Math.floor(len / 2);
  var left = arr.slice(0,mid);
  var right = arr.slice(mid,len);
  return merge(mergeSort(left),mergeSort(right));//递归分别对左右两部分数组进行排序合并
}
//合并
function merge(left,right){
    
    
  var result = [];
  while(left.length>0 && right.length>0){
    
    
    if(left[0]<=right[0]){
    
    
      //如果左边的数据小于右边的数据,将左边数据取出,放在新数组中
       result.push(left.shift());
    }else{
    
    
       result.push(right.shift());
    }
  }
  while(left.length){
    
    
     result.push(left.shift());
  }
  while(right.length){
    
    
     result.push(right.shift());
  }
  return result;
}
var arr = [6,4,3,7,8,1,2];
console.log(mergeSort(arr));//[1,2,3,4,6,7,8]

3.二叉树的遍历

二叉树(Binary Tree)是一种树形结构,它的特点是每个节点最多只有两个分支节点,一棵二叉树通常由根节点,分支节点,叶子节点组成。而每个分支节点也常常被称作为一棵子树。

在这里插入图片描述

  • 根节点:二叉树最顶层的节点
  • 分支节点:除了根节点以外且拥有叶子节点
  • 叶子节点:除了自身,没有其他子节点
  • 在二叉树中,我们常常还会用父节点和子节点来描述,比如图中2为6和3的父节点,反之6和3是2子节点

遍历二叉树(Traversing Binary Tree)是指按指定的规律对二叉树中的每个结点访问一次且仅访问一次

前序遍历(preorder):先自己,再遍历左节点,最后遍历右节点(根左右),下图遍历结果为:0134256

中序遍历(inorder):先左节点,再自己,最后右节点,输出的刚好是有序的列表(左根右),遍历结果为:3140526

后序遍历(postorder):先左节点,再右节点,最后自己(左右跟),遍历结果:3415620
在这里插入图片描述

3.1广度优先遍历

广度优先遍历(BFS):从二叉树的第一层(根结点)开始,自上至下逐层遍历;在同一层中,按照从左到右的顺序对结点逐一访问。遍历结果:0123456。

数据结构:队列。父节点入队,父节点出队列,先左子节点入队,后右子节点入队。递归遍历全部节点即可

广度优先遍历示意图:

在这里插入图片描述

  let tree = {
    
    
      value: 0,
      childLeft: {
    
    
        value: 1,
        childLeft: {
    
    
          value: 3
        },
        childRight: {
    
    
          value: 4
        }
      },
      childRight: {
    
    
        value: 2,
        childLeft: {
    
    
          value: 5
        },
        childRight: {
    
    
          value: 6
        }
      }
    }

    function ergodic(tree) {
    
    
      let list = [],
        queue = [tree]
      while (queue.length != 0) {
    
    
       // shift() 方法用于把数组的第一个元素从其中删除,并返回第一个元素的值。
        let target = queue.shift()
        list.push(target.value)
        if (target.childLeft) {
    
    
          queue.push(target.childLeft)
        }
        if (target.childRight) {
    
    
          queue.push(target.childRight)
        }
      }
      return list
    }
    console.log(ergodic(tree)); //[0,1,2,3,4,5,6]

3.2深度优先遍历

深度优先遍历(DFS):从根节点出发,沿着左子树方向进行纵向遍历,直到找到叶子节点为止。然后回溯到前一个节点,进行右子树节点的遍历,直到遍历完所有可达节点为止。

数据结构:栈

父节点入栈,父节点出栈,先右子节点入栈,后左子节点入栈。递归遍历全部节点即可

深度优先遍历示意图:

在这里插入图片描述

    let tree = {
    
    
      value: 0,
      childLeft: {
    
    
        value: 1,
        childLeft: {
    
    
          value: 3
        },
        childRight: {
    
    
          value: 4
        }
      },
      childRight: {
    
    
        value: 2,
        childLeft: {
    
    
          value: 5
        },
        childRight: {
    
    
          value: 6
        }
      }
    }

    function ergodic(tree) {
    
    
      let list = [],
        stack = [tree]
      while (stack.length != 0) {
    
    
        //pop()方法用于删除并返回数组的最后一个元素
        let target = stack.pop()
        list.push(target.value)
        if (target.childRight) {
    
    
         stack.push(target.childRight)
        }
        if (target.childLeft) {
    
    
          stack.push(target.childLeft)
        }
      }
      return list
    }
    console.log(ergodic(tree)); //[0,1,2,3,4,5,6]

4.数组扁平化

扁平化,顾名思义就是减少复杂性装饰,使其事物本身更简洁、简单,突出主题。
数组扁平化,将一个复杂的嵌套多层的数组,一层一层的转化为层级较少或者只有一层的数组。

   let arr = [
        [1, 2, 2],
        [3, 4, 5, 5],
        [6, 7, 8, 9, [11, 12, [12, 13, [14]]]],
        10,
      ];
      //方法一:ES6 flat将多维数组,降维,传的参数是多少就降多少维
      arr = arr.flat(Infinity); //Infinity正无穷大
      console.log(arr);
      //方法二:转化为字符串
      [1, [2, 3, [4]]].toString(); // "1,2,3,4"
      //split() 方法用于把一个字符串分割成字符串数组。
      "1,2,3,4".split(","); // ["1", "2", "3", "4"]
      //map()方法定义在JavaScript的Array中,它返回一个新的数组,数组中的元素为原始数组调用函数处理后的值。
      arr = arr.toString().split(",").map((item) => {
    
    
          return Number(item); //把每项都变成数字
        });
      //方法三:基于数组的some方法进行判断检测:判断某一类数组里面有没有这个东西,只要一个存在就返回true
      // Array.isArray检测数组存在
      while (arr.some((item) => Array.isArray(item))) {
    
    
        arr = [].concat(...arr);//...作用是遍历当前使用的对象能够访问到的所有属性,并将属性放入当前对象中
      }

5.斐波那契数列

斐波那契数列(兔子序列)1、1、2、3、5、8、13,21…

    //利用递归函数求:用户输入一个数字n就可以求出这个数字对应的兔子序列值
    //我们只需要知道用户输入的n的前面两项(n-1 n-2)就可以计算出n对应的序列值
    function fn(n) {
    
    
      if (n === 1 || n === 2) {
    
    
        return 1;
      }
      return fn(n - 1) + fn(n - 2);
    }
    console.log(fn(3)); //2
    console.log(fn(6)); //8

6.二分查找

二分法查找,也称折半查找,是一种在有序数组中查找特定元素的搜索算法。查找过程可以分为以下步骤:
(1)首先,从有序数组的中间的元素开始搜索,如果该元素正好是目标元素(即要查找的元素),则搜索过程结束,否则进行下一步。
(2)如果目标元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半区域查找,然后重复第一步的操作。
(3)如果某一步数组为空,则表示找不到目标元素。

function fn(arr, key) {
    
    
  var low = 0;
  var hight = arr.length - 1;
  while (low <= hight) {
    
    
  //parselnt()函数可解析一个字符串,并返回一个整数。
    var mid = parseInt((hight + low) / 2);
    if (key == arr[mid]) {
      return mid;
    } else if (key > arr[mid]) {
      low = mid + 1;
    } else {
      hight = mid - 1;
    }
  }
  return -1
}
var arr = [16, 25, 32, 46, 85, 67]
console.log(fn(arr, 25))

7.字符串中出现最多字符次数

function fn(str) {
    
    
  var temStr = "";
  var arr = new Array;
  arr = [0];
  for (var i = 0; i < str.length - 1; i++) {
    
    
    temStr = str.charAt(i);
    if (str.split(temStr).length > arr[0]) {
    
    
      arr[0] = str.split(temStr).length - 1;
      arr[1] = temStr;
    }
  }
  return arr
}
console.log(fn('qweqrtyuiqqqwrtyudfgerqtywer'))//[6,'q']

猜你喜欢

转载自blog.csdn.net/Better_Xing/article/details/114937915
今日推荐