【小番茄夫斯基】全网最全前端面试手撕算法题,原理手写汇总

文章目录

  • 深拷贝
  • 柯里化函数
  • 实现 instanceof
  • 手写 new
  • 数组去重
  • flat 拍平数组
  • ObjectDefineProperty实现双向数据绑定
  • setInterval 实现 setTimeout
  • setTimeout 实现 setInterval
  • this 绑定 apply,call,bind
  • apply
  • call
  • bind
  • 手写 promise
  • promissAll
  • promiseFinally
  • promiseRace
  • promiseReject
  • prmiseResole
  • 防抖,节流
  • 排序算法
  • 快速排序
  • 选择排序
  • 插入排序
  • 归并排序
  • 冒泡排序
  • 希尔排序
  • 继承
  • 原型链继承
  • 构造函数继承
  • 组合继承(原型链继承 + 构造函数继承)
  • 原型式继承
  • 寄生式继承
  • 组合寄生式继承
  • ES6 继承
  • 算法题
  • 大数相加
  • 哈夫曼树
  • 千分位分隔符
  • 最大公约数

深拷贝

// 简单版本
// function deepClone1(obj) {
//   return JSON.parse(JSON.stringify(obj));
// }



function deepClone(obj = {}) {
  if (typeof obj !== "object" && obj !== null) return obj;
  let result = obj instanceof Array ? [] : {};

  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      result[key] = deepClone(obj[key]);
    }
  }
  return result;
}

let obj = {
  name: "kunyuan",
  age: 15,
  da: {
    name: "45",
  },
};

let newObj = deepClone(obj);
newObj.name = "laj";
newObj.da.name = "aaaaaaaa";
console.log(obj);
console.log(newObj);
<img src="" alt="" width="30%" />

柯里化函数

// 第一种:固定传入参数,参数够了才执行

/** * 实现要点:柯里化函数接收到足够参数后,就会执行原函数,那么我们如何去确定何时达到足够的参数 呢?
 * * 柯里化函数需要记住你已经给过他的参数,如果没给的话,则默认为一个空数组。
 * * 接下来每次调用的时候,需要检查参数是否给够,如果够了,则执行fn,
 * 没有的话则返回一个新的 curry 函数,将现有的参数塞给他。 **/

// 待柯里化处理的函数
let sum = (a, b, c, d) => {
  return a + b + c + d;
};

// 柯里化函数,返回一个被处理过的函数

let curry = (fn,...args) => { 
  // arr 记录已有参数
  return args.length >= fn.length ? fn(...args) : (...arr)=> curry(fn,...args.concat(...arr))
}

var sumPlus = curry(sum);

console.log(sumPlus(1)(2)(3)(4));
console.log(sumPlus(1, 2)(3)(4));
console.log(sumPlus(1, 2, 3)(4));


实现 instanceof

function myInstanceOf(left, right) {
  let leftValue = left.__proto__;
  let rightValue = right.prototype;

  while (true) {
    if (leftValue == null) return false;
    if (leftValue === rightValue) {
      return true;
    }
    leftValue = leftValue.__proto__;
  }
}

function getType(type) {
  return Object.prototype.toString.call(type).slice(8, -1);
}

console.log(myInstanceOf(Object, Function));
console.log(getType(1));

手写 new

function mynew(func, ...args) {

  let obj = {};
  obj.__proto__ = func.prototype;
  let result = func.apply(obj, args);
  return result instanceof Object ? result : obj; 
}



function Person(name, age) {
  this.name = name;
  this.age = age;
}

Person.prototype.say = function () {
  console.log(this.name + ' ' + this.age);
}

let p = mynew(Person, "邬坤源", 12);
console.log(p);
p.say();

数组去重

var array = [1, 2, 1, 1, '1'];


// Array.from去重
function uniques(array){
  return Array.from(new Set(array));   
}

// 简化
function SampleUniques(array){
  return [...new Set(array)]
}
console.log(uniques(array));

// 也可以使用es5中的indexOf方法
function es5Uniques(array){
  let res  = array.filter(function(item,index,array){
    return array.indexOf(item) === index;
  })
  return res;
}

console.log("es5去重");
console.log(es5Uniques(array));

flat 拍平数组

const arr = [1, [2, 3, [4, [[5]]]]]

console.log(arr.flat(1));
console.log(arr.flat(2));
console.log(arr.flat(3));
console.log(arr.flat(4));

// depth<=0时,返回的数组和原数组维数一样(注意只是维数一样,空位情况见第3点)
console.log("depth<=0时,返回的数组和原数组维数一样(注意只是维数一样,空位情况见第3点)");
console.log(arr.flat(0));

console.log([].concat(...arr));

// 自己实现一个flat扁平化数组
function myflat(arr){
  while(arr.some(item=>Array.isArray(item))){
      arr = [].concat(...arr)
  }
  return arr;
}
console.log("我的实现");
console.log(myflat(arr));

// 重写原型上的方法
Array.prototype.newflat = function(n=1){

  let arr = this;
  while(n && this.some(item=>Array.isArray(item))){
    arr = [].concat(...arr);
    n--;
  }
  return arr;
}
console.log("重写原型上的方法");
console.log([1, 2, [3, 4, [5, [6, [7]]]]].newflat(5))


// 使用reduce方法拍平数组
function FlatReduce(arr){
  return arr.reduce((pre,cur) => {
    return pre.concat(Array.isArray(cur)?FlatReduce(cur):cur)
  },[])
}

ObjectDefineProperty实现双向数据绑定

DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Documenttitle>
head>

<body>
  <div id="app">
    <input type="text" value="" name="txt" id="txt">
    <p id="Show_text">p>
  div>

  <script>
    var obj = {};
    Object.defineProperty(obj, 'value', {
      get: function () {
        return obj;
      },
      set: function (newValue) {
        document.getElementById('txt').value = newValue
        document.getElementById('Show_text').innerHTML = newValue
      },

    })
    document.addEventListener('keyup', function (e) {
      obj.value = e.target.value
    })
  script>
body>

html>

setInterval 实现 setTimeout

const mySetTimeout = (fn, delay) => {
  const timer = setInterval(() => {
    fn()
    clearInterval(timer)
  }, delay)
}

setTimeout 实现 setInterval

const mySetInterval = (fn, delay) => {
  let timer = null
  const interval = () => {
    fn()
    timer = setTimeout(interval, delay)
  }
  timer = setTimeout(interval, delay)

  return {
    cancel: () => {
      clearTimeout(timer)
    }
  }
}

this 绑定 apply,call,bind

apply

Function.prototype.myApply = function(context=globalThis,...args){

  let key = Symbol('key');
  context[key] = this;
  let result = context[key](args);
  delete context[key];
  return result;
}

function f(a,b){
  console.log(a+b)
  console.log(this.name)
 }

 let obj={
  name:'张三'
 }
 f.myApply(obj,[1,2])

call

Function.prototype.newCall = function(context=globalThis,...args) {

  let key = Symbol('key')
  context[key] = this
  let result = context[key](...args)
  delete context[key]
  return result;  
}
function f(a,b){
  console.log(a+b)
  console.log(this.name)
 }

 let obj={
  name:1
 }
 f.newCall(obj,1,2)

bind

const obj = {
  name: "11",
  fun() {
    console.log(this.name);
  },
};
Function.prototype._bind = function (ctx, ...args) {
  // 获取函数体
  const _self = this;
  // 用一个新函数包裹,避免立即执行
  const bindFn = (...reset) => {
    return _self.call(ctx, ...args, ...reset);
  };
  return bindFn;
};
const obj2 = { name: "22" };
obj.fun(); // 11
const fn = obj.fun.bind(obj2);
const fn2 = obj.fun._bind(obj2);
fn(); // 22
fn2(); // 22

手写 promise

promissAll

function promiseAll(promises){
  if(!Array.isArray(promises)){
    throw new TypeError("promises must be an array")
  }

  return new Promise(function(resolve, reject){
    // 数组长度
    let promiseNum = promises.length;
    // 成功的数量
    let resolveCount  = 0;
    // 成功的值的数组
    let resolveValues = new Array(promiseNum);
    // 先遍历
    for(let i=0; i<promiseNum; i++){
      // 为什么不直接 promise[i].then, 因为promise[i]可能不是一个promise
      Promise.resolve(promises[i]).then(function(value){
        resolveValues[i] = value;
        resolveCount++;
        if(resolveCount == promiseNum){
          return resolve(resolveValues)
        }
      },
      function(err){
        return reject(err);
      }
      )
    }
  })
}

promiseFinally

Promise.prototype.finally = function(callback) {
  this.then(value => {
    return Promise.resolve(callback()).then(() => {
      return value;
    })
  }, error => {
    return Promise.resolve(callback()).then(() => {
      throw error;
    })
  })
}

promiseRace

// function promiceRaces(promises){

//   if(!Array.isArray(promises)){
//     throw new TypeError("promise must be an array")
//   }
//   return new Promise(function(resolve, reject){
//     promises.forEach( p =>
//       Promise.resolve(p).then(data=>{
//         resolve(data)
//       },err =>{
//         reject(err)
//       }
//       )
      
//       )
//   })

// }


function PromiseRace(promises) {

  if(promises instanceof Array){
    throw new TypeError("promises must be an array")
  }

  return new Promise(function(resole,reject){
    promises.forEach( item =>{
        Promise.resolve(item).then(data=>{
          resole(data)
        },
        err =>{
          reject(err)
        }
        )
    })
  })

}

promiseReject

Promise.reject = function(reason){
 
  return new Promise((resolve, reject)=>reject(reason))
}

prmiseResole

Promise.resolve = function(value) {

  if(value instanceof Promise){
    return value;
  }
  return new Promise((resolve, reject)=>resolve(value))

}

防抖,节流

// 防抖
// 防抖: n 秒后在执行该事件,若在 n 秒内被重复触发,则重新计时


const debounce = (func,wait = 500) => {
  var timer = 0;
  return function(...args) {
    if(timer) clearTimeout(timer);
    timer = setTimeout(()=>{
      func.apply(this,args);
    },wait);
  }
}

const fn = debounce(()=>{console.log(3);},3000)
setInterval(fn, 2000)


// 节流
// 节流: n 秒内只运行一次,若在 n 秒内重复触发,只有一次生效


// func是用户传入需要防抖的函数
// wait是等待时间
const throttle = (func, wait = 500) => {
  // 上一次执行该函数的时间
  let lastTime = 0
  return function (...args) {
    // 当前时间
    let now = +new Date()
    // 将当前时间和上一次执行函数时间对比
    // 如果差值大于设置的等待时间就执行函数
    if (now - lastTime > wait) {
      lastTime = now
      func.apply(this, args)
    }
  }
}

const fn = throttle(() => {
  console.log(3);
}, 3000)

setInterval(fn, 2000)

// 适用场景:

// 拖拽场景:固定时间内只执行一次,防止超高频次触发位置变动
// 缩放场景:监控浏览器resize
// 动画场景:避免短时间内多次触发动画引起性能问题

排序算法

快速排序

// 快速排序
function QuickSort(arr) {

    const n = arr.length;
    if (n <= 1) return arr;
    let pivot = arr[0];

    let left = [];
    let right = [];

    for (let i = 1; i < n; i++) {
        if (arr[i] < pivot) {
            left.push(arr[i]);
        } else {
            right.push(arr[i]);
        }
    }
    // return QuickSort(left).concat([pivot], QuickSort(right));
    return [...QuickSort(left), pivot, ...QuickSort(right)]
}
let list = [4, 6, 8, 5, 9, 1, 2, 3, 2];

let sortArr = QuickSort(list)
console.log("快速排序", sortArr);

选择排序

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

插入排序

function insertionSort(arr) {
    var len = arr.length;
    var preIndex, current;
    for (var i = 1; i < len; i++) {
        preIndex = i - 1;
        current = arr[i];
        while (preIndex >= 0 && arr[preIndex] > current) {
            arr[preIndex + 1] = arr[preIndex];
            preIndex--;
        }
        arr[preIndex + 1] = current;
    }
    return arr;
}

归并排序

const mergeSort = arr => {
    //采用自上而下的递归方法
    const len = arr.length;
    if (len < 2) {
        return arr;
    }
    // length >> 1 和 Math.floor(len / 2) 等价
    let middle = Math.floor(len / 2),
        left = arr.slice(0, middle),
        right = arr.slice(middle); // 拆分为两个子数组
    return merge(mergeSort(left), mergeSort(right));
};

const merge = (left, right) => {
    const result = [];

    while (left.length && right.length) {
        // 注意: 判断的条件是小于或等于,如果只是小于,那么排序将不稳定.    
        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;
};

// 测试
const arr = [3, 44, 38, 5, 47, 15, 36, 26, 27, 2, 46, 4, 19, 50, 48];
console.time('归并排序耗时');
console.log('arr :', mergeSort(arr));
console.timeEnd('归并排序耗时');
// arr : [2, 3, 4, 5, 15, 19, 26, 27, 36, 38, 44, 46, 47, 48, 50]
// 归并排序耗时: 0.739990234375ms

冒泡排序

function bubbleSort(arr) {
    var len = arr.length;
    for (var i = 0; i < len - 1; 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;
}

希尔排序

function shellSort(arr) {
    // 1. 获取数组长度
    let length = arr.length

    // 2.获取初始的间隔长度
    let interval = Math.floor(length / 2)

    // 3. 不断地缩小间隔的大小,进行分组插入排序
    while (interval >= 1) {

        // 4. 从 arr[interval] 开始往后遍历,将遍历到的数据与其小组进行插入排序
        for (let i = interval; i < length; i++) {
            let temp = arr[i]
            let j = i
            while (arr[j - interval] > temp && j - interval >= 0) {
                arr[j] = arr[j - interval]
                j -= interval
            }

            arr[j] = temp
        }

        // 5. 缩小间隔
        interval = Math.floor(interval / 2)
    }

    return arr
}

继承

原型链继承

function Parent(){
  this.name = '邬坤源',
  this.play = [1,2,3]
}

function Child(){
  this.age = 29
}

Child.prototype = new Parent();

let myChild = new Child();

console.log(myChild.name);

// 缺点:改变s1的play属性,会发现s2也跟着发生变化了,这是因为两个实例使用的是同一个原型对象,内存空间是共享的

var s1 = new Child();
var s2 = new Child();
s1.play.push(4);
console.log(s1.play, s2.play); // [1,2,3,4]

构造函数继承

function Parent(){
  this.name = 'parent1';
}

Parent.prototype.getName = function () {
  return this.name;
}

function Child(){
  Parent.call(this);
  this.type = 'child'
}

let child = new Child();

console.log(child);

// 缺点 :父类原型对象中一旦存在父类之前自己定义的方法,那么子类将无法继承这些方法
console.log(child.getName());

组合继承(原型链继承 + 构造函数继承)

// 组合式继承就是把原型链继承和函数式继承结合起来

function Parent3 () {
  this.name = 'parent3';
  this.play = [1, 2, 3];
}

Parent3.prototype.getName = function () {
  return this.name;
}

function Child3() {
  // 第二次调用 Parent3()
  Parent3.call(this);
  this.type = 'child3';
}


// 第一次调用 Parent3()
Child3.prototype = new Parent3();

// 手动挂上构造器,指向自己的构造函数
// Child3.prototype.constructor = Child3;
var s3 = new Child3();
var s4 = new Child3();
s3.play.push(4);
console.log(s3.play, s4.play);  // 不互相影响
console.log(s3.getName()); // 正常输出'parent3'
console.log(s4.getName()); // 正常输出'parent3'

原型式继承

这里主要借助Object.create方法实现普通对象的继承

let parent = {
  name: 'li',
  friends: ['ha','mo','gui'],
  getName:function(){
    return this.name;
  }
}

let person = object.create(parent);
person4.name = "tom";
person4.friends.push("jerry");
console.log(person4.name); // tom
console.log(person4.name === person4.getName()); // true


let person5 = Object.create(parent4);
person5.friends.push("lucy");
console.log(person5.name); // parent4

console.log(person4.friends); // ["p1", "p2", "p3","jerry","lucy"]
console.log(person5.friends); // ["p1", "p2", "p3","jerry","lucy"]

  // 缺点:因为Object.create方法实现的是浅拷贝,多个实例的引用类型属性指向相同的内存,存在篡改的可能

寄生式继承

let parent5 = {
  name: "parent5",
  friends: ["p1", "p2", "p3"],
  getName: function() {
      return this.name;
  }
};

function clone(original) {
  let clone = Object.create(original);
  clone.getFriends = function() {
      return this.friends;
  };
  return clone;
}

let person5 = clone(parent5);

console.log(person5.getName()); // parent5
console.log(person5.getFriends()); // ["p1", "p2", "p3"]

组合寄生式继承

function Parent6() {
  this.name = 'parent6';
  this.play = [1, 2, 3];
}
Parent6.prototype.getName = function () {
  return this.name;
}
function Child6() {
  Parent6.call(this);
  this.friends = 'child5';
}

function clone (parent, child) {
  // 这里改用 Object.create 就可以减少组合继承中多进行一次构造的过程
  child.prototype = Object.create(parent.prototype);
  child.prototype.constructor = child;
}

clone(Parent6, Child6);

Child6.prototype.getFriends = function () {
  return this.friends;
}

let person6 = new Child6();
console.log(person6); //{friends:"child5",name:"child5",play:[1,2,3],__proto__:Parent6}
console.log(person6.getName()); // parent6
console.log(person6.getFriends()); // child5

ES6 继承

class Person {
  constructor(name) {
    this.name = name
  }
  // 原型方法
  // 即 Person.prototype.getName = function() { }
  // 下面可以简写为 getName() {...}
  getName = function () {
    console.log('Person:', this.name)
  }
}
class Gamer extends Person {
  constructor(name, age) {
    // 子类中存在构造函数,则需要在使用“this”之前首先调用 super()。
    super(name)
    this.age = age
  }
}
const asuna = new Gamer('Asuna', 20)
asuna.getName() // 成功访问到父类的方法

算法题

大数相加

/*
    大数相加的意思是超过 JS 存放数据的安全范围(一般指的是数字)的数字进行相加
*/

console.log(Number.MAX_SAFE_INTEGER) //9007199254740991
console.log(Number.MIN_SAFE_INTEGER) //-9007199254740991

// 9007199254740991 + 1234567899999999999

let a = "9007199254740991", b = '1234567899999999999'

function add(a, b) {
    // 位数补全
    const MAX_LEN = Math.max(a.length, b.length)

    // padStart() 方法用于从字符串左侧填充 0
    a = a.padStart(MAX_LEN, 0)
    b = b.padStart(MAX_LEN, 0)

    let flag = 0 // 进位标志
    let str = '', j = MAX_LEN-1

    while(j >= 0) {
        let res = Number(a[j]) + Number(b[j]) + flag
        flag = res >= 10 ? 1 : 0
        res = res % 10
        str = res + str
        j--
    }

    // 处理最高位溢出
    if(flag === 1) {//增加多一位
        str = '1' + str
    }

    return str
}

// 正确结果:1243575099254740990
// 输出答案:1243575099254740990
console.log(add(a, b))

哈夫曼树

/*
    哈夫曼树 —— 最优二叉树
*/

/**
 * @param {*} val 
 * @param {*} left 
 * @param {*} right 
 */

function TreeNode(val, char, left, right) {
    this.val = val || 0  // 字符出现的次数
    this.char = char || '' // 待编码的字符(当前节点是叶子节点才给char赋值)
    this.left = left || null    
    this.right = right || null
}


/**
 * 
 * @param {Map} map 
 * @returns 
 */

function HuffmanTree(str) {
    if(str === '') { return null }

    //1. 统计字符出现的频率
    let hash = {}

    for(let i=0; i<str.length; i++) {
        hash[str[i]] ??= 0 // 前者为 null / undefined 才赋值
        hash[str[i]] = hash[str[i]] + 1
    }

    //2. 构造哈夫曼树
    const huffmanTree = this.getHuffmanTree(hash)
    console.log('===哈夫曼树===', huffmanTree)

    //3. 遍历哈夫曼树得到编码表
    const map = this.getHuffmanCode(huffmanTree)
    console.log('===哈夫曼编码表===', map)

    //4. 根据编码对照表,返回最终的二进制代码
    let res = ''
    for(let item in hash) {
        res += map.get(item)
    }
    console.log('===哈夫曼总编码===', res)
}

HuffmanTree.prototype.getHuffmanTree = function(hash) {
    // 构建叶子节点
    let forest = []
    for(let char in hash) {
        const node = new TreeNode(hash[char], char)
        forest.push(node)
    }

    console.log(forest)
    
    let allNodes = []
    while(forest.length != 1) {
        forest.sort((a, b) => a.val - b.val)
        let node = new TreeNode(forest[0].val + forest[1].val)
        allNodes.push(forest[0])
        allNodes.push(forest[1])
        node.left = allNodes[allNodes.length - 2] // 左子树放置词频低的
        node.right = allNodes[allNodes.length - 1] // 右子树放置词频高的

        forest = forest.slice(2)
        forest.push(node) // 将新生成的节点放入森林中
    }

    return forest[0] // 整棵树的根节点
}

// 树的遍历(只统计子结点)
HuffmanTree.prototype.getHuffmanCode = function(huffmanTree) {
    let map = new Map()

    // 层数大于二才有路径
    const search = (node, curPath) => {
        if(!node) { return }
        if(!node.left && !node.right) {
            map.set(node.char, curPath)
        }

        if(node.left) {
            search(node.left, curPath + '0')
        }
        if(node.right) {
            search(node.right, curPath + '1')
        }
    }

    search(huffmanTree, '')
    return map
}


const huff = new HuffmanTree('ABBCCCDDDDEEEEE')

千分位分隔符

/*
    实现对数字进行千位分隔符的分隔
    19,351,235.235767
*/


// 将数字转为字符串,返回的是字符串
var throusandDot = function(num) {
    num = String(num)
    let [zheng, xiao] = num.split('.') // 切成整数部分和小数部分

    console.log(zheng, xiao)
    
    let sum = 0, res = []
    for(let i=zheng.length-1; i>=0; i--) {
        res.push(zheng[i])
        sum++
        if(sum === 3 && i!=0) { res.push(','); sum = 0 }
    }

    return res.reverse().join('') + '.' + xiao
}

console.log(throusandDot(119351235.235767))



/*
    直接使用 api => 可能出现的问题是位数比较多的时候会被截取
*/
var throusandDot = function(num) {
    return num.toLocaleString()
}

console.log(throusandDot(119351235.235767))


/*
使用正则表达式
https://www.runoob.com/regexp/regexp-syntax.html
*/
var throusandDot = function(num) {
    var res=num.toString().replace(/\d+/, function(n){ // 先提取整数部分
        return n.replace(/(\d)(?=(\d{3})+$)/g, function($1){ //?= 匹配的是前面的内容 
            // console.log('$1', $1)
           return $1+",";
         });
   })
   return res;
}



console.log(throusandDot(119351235.235767))

最大公约数

/*
    求最大公约数
    1. 辗转相除法
    2. 更相减损法
    3. 普通解法
*/


// 辗转相除法
function greatestCommonDivisor(a, b) {
    if(b == 0) {
        return a
    }
    return greatestCommonDivisor(b, a % b)
}

// 更相减损法
function greatestCommonDivisor2(a, b) {
    if(a > b) {
        a -= b
    } else if(a < b) {
        b -= a
    } else {
        return a
    }
}

// 普通解法

更多算法实现可移步我的 GitHub,欢迎 start
https://github.com/tomato-wu/JS-algorithm.git

你可能感兴趣的:(前端,前端,面试,算法)