弑君者

V1

2022/04/25阅读:30主题:默认主题

铜三铁四还不抓紧手撕代码!!!

金三银四在今年彻底成了铜三铁四,不管在职的还是离职的,都在瑟瑟发抖。 看下这张图,应该会有一个很直观的感受😁

大环境你无力抵抗,能做的只能提高自己,躺平更不可能,穷逼是没资格躺平的😁。

既然无法躺平,只能做到人无我有,人有我优!

一道特别简单的数组map方法面试题

大家可以看下这段代码返回的结果是什么?为什么?

let a = [1,2,3];
a = a.map((item) => {
  item +=2;
});
console.log(a);

无限add

这是最常见的一道面试题,出现的概率是非常非常高

比如让你实现如下效果

console.log(add(1,2, 3)) // 打印6 console.log(add(1)(2,3)) // 打印6 console.log(add(1)(3)(2)) // 打印6 console.log(add(1,2)(3)) // 打印6

假如是你,可以考虑下如何实现?

我的想法是通过一个数组收集依赖,不管调用多少次,可以都把参数都push到数组中,最后求结果的时候,可以求和。

但是现在有个问题,就是什么时候求和呢?

只有使用valueOf或者toString了,如果没有想到valueOf或者toString也没啥关系,第一次遇到一般很少有人能想到。

代码如下

function add({
  let res = [...arguments];
  function resultFn({
    res = res.concat([...arguments]);
    return resultFn;
  }
  resultFn.valueOf = function({
    return res.reduce((a,b) =>a+b);
  }
  return resultFn;
}
console.log(5+add(1,2)(3)(4))
// 输出15 

如何缓存前面的结果

类似如下 add(1)(2)(3) 输出6, 当add(1,2)(3)的时候,不需要重新计算直接从缓存里边拿到结果?

这个可以自己思考下

function add({
  let res = [...arguments];
  if(!add.map) {
    add.map = new Map();
  }
  function resultFn({
    res = res.concat([...arguments]);
    return resultFn;
  }
  resultFn.valueOf = function({
    const curStr = res.join("");
    if(add.map.has(curStr)) {
      return add.map.get(curStr);
    }else {
      const result = res.reduce((a,b) =>a+b)
      add.map.set(curStr,result);
      return result;
    }
  }
  return resultFn;
}
console.log(add(1,2)(3)(4).valueOf())
console.log(add(1,2,3)(4).valueOf())

逆转链表

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

这也是面试中经常会遇到的一道题目,题目倒不是很难,是leetcode的一道easy题目。

这里的思想很简单,就是采用递归。

递归在算法中是一个很重要的思想,很多算法都可以采用递归思想解决。如果想使用递归思想解决问题,可以记住以下两点方法,会事半功倍。

  1. 递归想清楚什么边界条件下退出。
  2. 可以写几个测试用例,从最简单的开始使用递归解决试试。
/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */

/**
 * @param {ListNode} head
 * @return {ListNode}
 */

var reverseList = function(head{
    if(!head) {
        return null;
    }
    if(head && !head.next) {
        return head;
    }
    const pre = head;
    const cur = head.next;
    const newHead = reverseList(head.next);
    cur.next = pre;
    pre.next = null;
    return newHead;
};

洗牌函数

洗牌算法也是面试经常会遇到的一道题目,出现的频率也是蛮高的。

我有次也被面到了这道题目,但是呢,当时自己自作聪明,考虑太多了,考虑Math.random能不能做到完全随机😂

其实这道算法只要想明白了使用Math.random完全随机就可以了

function shuffle(arr=[]{

  for(let j = arr.length-1;j>=0;j--) {
    const randomIndex = Math.floor(Math.random() * j+1);
    const temp = arr[j];
    arr[j] = arr[randomIndex];
    arr[randomIndex] = temp;
  }
  return arr;
}
console.log(shuffle([1,2,3,4,5]));

手写call apply bind

这三个函数在面试中出现的频率也是非常非常高,所以这个基本上是如果想出去面试是必须要掌握的,而且要掌握到和喝水吃饭一样自然😁

call 实现其实不是很难,如果觉得很难的话,可以先想想哪里觉得难,找到点之后,才能对症下药。

function myCall(ctx,...args{
  ctx = ctx || window;
  const that = this;
  ctx.fn = that;
  const res = ctx.fn(...args);
  delete ctx.fn;
  return res;
}
Function.prototype.myCall= myCall;
console.log.myCall(null,1,2);

剩下的apply和bind实现的原理基本上差不多,可以自己实现。

深拷贝

深拷贝和浅拷贝这个也是面试频率非常高的面试题,浅拷贝比较好实现,深拷贝相对来说比较难点。

写一个简单深拷贝,我相信很多人都可以写出来,而且其实在项目中使用基本上没有啥问题。但是如果想要写一个完整的深拷贝,需要考虑各种情况的处理,如果在平时可能没啥问题,但是如果面试的时候,可能就会一深入进去,就会卡住。

简单的实现

function deepClone(target{
    if (typeof target === 'object' && target !== null) {
        const cloneTarget = Array.isArray(target) ? [] : {};
        for (const key in target) {
            cloneTarget[key] = deepClone(target[key]);
        }
        return cloneTarget;
    } else {
        return target;
    }
  };
  const a = {"ss":1"bb":[1,2,4]};
  const b = deepClone(a);
  b.ss = 333;
  console.log(b);
  console.log(a);

如果想要写的更完美一些,就会需要考虑一下一些特殊类型和特殊场景。

  1. 如果需要拷贝的对象里边有循环怎么办,类似于如下情况
const a = {
  c1,
  b: [2,3],
};
a.self = a;

这种情况假如是你如何处理呢?(其实这种在真实业务场景很少会遇到)

这种情况其实就是循环,解决这种办法很直接,就是把循环给切断,不然那就是无限循环了,

或者想下有没有其它的方法,使用map缓存是不是也可以?

切断循环可以判断当前的值是不是指向自身来判断,代码如下

target[key] === target

至于还有其它一些特殊数据类型的处理,比如Symbol,函数等,可以自己去查下,其实写一个比较完整的深拷贝还是有点难度的,可以去看下loadsh如何实现的。

手写debounce和throttle函数

debounce和throttle 基本上面试都会被面到,这两个可以说是面试必问的超级高频题。

debounce和throttle的区别以前看了很多文章,总是模模糊糊的没有明确的搞明白区别,后来总算明白了,两者的区别其实就是throttle会在一段时间内肯定会触发一次,debounce是不断触发的情况下,只触发最后一次。

当然这是比较简单的版本的

function throttle(fn,delay{
  let timeId = null;
  return function({
    if(timeId) {
      return;
    }
    timeId = setTimeout(function({
      fn.apply(this,arguments);
      clearTimeout(timeId);
    },delay);
  }
}
function debounce(fn,delay{
  let timeId = null;
  return function({
    if(timeId) {
      clearTimeout(timeId);
    }
    timeId = setTimeout(function({
      fn.apply(this,arguments);
      clearTimeout(timeId);
    },delay);
  }
}

寻找二叉树中的最近的公共祖先

给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

这也是在面试中经常会被问到的问题

题目其实不是很难,但是在面试的氛围中的时候,可能就不一样了,但是这个没有啥好的办法,只能在平时多练多思考多总结,台上一分钟,台下十年功,没有其它的捷径。

二叉树的算法大多数都可以使用递归解决,我当时遇到的时候,其实也已经想到使用递归解决,但是没有想明白使用递归的基本条件---那就是需要找到何时结束递归,也就是找到递归的边界条件。

/**
 * Definition for a binary tree node.
 * function TreeNode(val) {
 *     this.val = val;
 *     this.left = this.right = null;
 * }
 */

/**
 * @param {TreeNode} root
 * @param {TreeNode} p
 * @param {TreeNode} q
 * @return {TreeNode}
 */

var lowestCommonAncestor = function(root, p, q{
    if(root === null || root.val === p.val || root.val === q.val ) {
        return root;
    }
    const leftNode = lowestCommonAncestor(root.left,p,q);
    const rightNode = lowestCommonAncestor(root.right,p,q);
    if(leftNode && rightNode) {
        return root;
    }
    if(leftNode && !rightNode) {
        return leftNode;
    }
    if(!leftNode && rightNode) {
        return rightNode;
    }
};

Promise.all 实现

面试的时候一般不会让你手写Promise,除非有点变态的公司,大多数都是让你说下如何实现原理就可以了,这个大家可以自己去搜下,很多比较好的文章。

不过Promise.all 这个在面试中出现的手写的概率是非常高的,这个必须要达到可以熟练手写的程度,不然有时候面试就是浪费自己的时间,

现在大裁员的背景下,面试官选择的多了,可能会因为任何一点就把你挂掉,自己能做的就是人无我有,人有我优,才能脱颖而出。

Promise.all 实现原理其实就是根据传入的数组b,然后

Promise.myAll = function(arr = []{
  if(arr.length === 0return Promise.resolve();
  return new Promise((resolve,reject) => {
    let res = [];
    let allCount = arr.length;

    for(let i =0;i<arr.length;i++) {
      arr[i].then((curRes)=> {
        res.push(curRes)
        if(res.length === allCount) {
          return resolve(res);
        }
      }).catch((err) =>{
        return reject();
      });
    }
  })
}
const a1 = new Promise((resolve,reject)=> {
  console.log(11)
  setTimeout(() => {
    resolve(1)
  },1000)
})
const b1 = new Promise((resolve,reject)=> {
  console.log(22)
  setTimeout(() => {
    resolve(3)
  },3000)
})
Promise.myAll([a1,b1]).then((res)=> {
  console.log(333,res)
}).catch((err) => {
  console.log(77,err)
})

数组去重

这个在面试中出现的概率也是超级高的,很多时候你会明显感觉到当面试官没有别的问题可以问的时候,可能就会不由自主的转到这道题目上去。

数组去重本身不是很难,甚至可以说很简单,但是这个时候可能不是你实现就可以的,一般后面都会加一句,“有没有其它方法,还有没有其它方法”, 所以自己可以多准备几种。

面试就是要get到面试官的点在哪里,挠到他的痒处,不然可能就会出现牛头不对马嘴,或者自己觉得答的很好,为啥没有过的情况。

function deleteSame(arr=[]{
  return Array.from(new Set(arr))
}
const arr1 = [1,1,3,4,55,65,65,1,3,55];
console.log(deleteSame(arr1));

合并区间

以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。

实例1

输入:intervals = [[1,3],[2,6],[8,10],[15,18]]
输出:[[1,6],[8,10],[15,18]]
解释:区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].

实例2

输入:intervals = [[1,4],[4,5]]
输出:[[1,5]]
解释:区间 [1,4] 和 [4,5] 可被视为重叠区间。

该题目倒不是很难,但是真正在面试中如果遇到,恐怕很少能在规定的时间内完成ac

这就是典型的台上一分钟,台下十年功,平时在刷题的时候,可以使用focus计算时间,这种题目控制在十分钟之内。

该题目就是先排序,然后合并区间,思想其实很简单

/**
 * @param {number[][]} intervals
 * @return {number[][]}
 */

var merge = function(intervals{
    if(!Array.isArray(intervals) || !intervals) return [];

    if(intervals.length === 1 || intervals.length ===0return intervals;

    intervals.sort((a,b) =>{
        if(a[0] !== b[0]) {
            return a[0]-b[0];
        }else {
            return b[1]-a[1];
        }
    })

    const res = [];
    for(let i =0;i<intervals.length;) {
        const curRes = [];
        curRes[0] = intervals[i][0];
        curRes[1] = intervals[i][1];
        let pre = i;
        for(let j = i+1;j<intervals.length;j++) {
            if(intervals[j][0]<=curRes[1]) {
                curRes[1] = Math.max(intervals[j][1],curRes[1]);
                i = j+1;
            }else {
                break;
            }
        }
        if(pre === i) {
            i++;
        }
        
        res.push(curRes)
    }
    return res;
};

修改异步add函数

现在有其它团队提供的异步接口,可以

await asyncAdd = (a, b, (err, res) => {
   // 利用网络请求实现a+b,成功结果返回res
})

现需要改进该api,利用其实现一个add方法,使其能够实现多个数相加(写主要思路即可)(时间复杂度为logn)

function mulipleAsyncAdd(a,b,c...{
    //Todo
}

如果想要实现lgn肯定是并行发送,如果想要并行发送,目前我们知道的是通过Promise.all来实现。

整体的逻辑很简单,就是使用Promise.all 并行执行,当检测到最后还剩一个结果的时候就直接返回结果。

function asyncAdd(a,b{
  return Promise.resolve(a+b)
}
function mulipleAsyncAdd({
  const promisArr = [];
  const nums =[...arguments];
  return new Promise((resolve,reject) => {
    if(nums.length===1) {
      resolve(nums[0]);
    }else {
      for(let i =0;i<nums.length;i +=2) {
        if( (i+1) === nums.length) {
          promisArr.push(asyncAdd(nums[i], 0));
        }else {
          promisArr.push(asyncAdd(nums[i], nums[i+1]));
        }
      }
      Promise.all(promisArr)
      .then(res => {
        return mulipleAsyncAdd(...res);
      }).then((res) => {
        resolve(res);
      })
    }
  })
}
// test
(async () => {
  console.log(111);
  const res = await mulipleAsyncAdd(1,2,2,3,4,5,6);
  console.log(3333,res);
  console.log("end")
})()

分类:

前端

标签:

JavaScript

作者介绍

弑君者
V1