面试:百度一面,吓尿后

如果你也在准备春招,欢迎加我微信shunwuyu。这里有几十位一心去大厂的友友可以相互鼓励,分享信息,模拟面试,共读源码,齐刷算法,手撕面经。

前言

百度一面后, 可能由于过度紧张,这一两天过的好恍惚。那位又秃又强的面试官又来梦里,睡个屁。起来复习,一面要是通过,二面会考点什么?各位可以看看面试:百度一面,吓尿了 - 掘金 (juejin.cn)我的一面考题,帮我支个招。

南无阿弥陀佛! 前几天去求了佛,保我百度有二面,二面之前再给我一周准备时间。

百度一面,吓尿后

请教学长

找到内推我的学长,他细心地了解我的面试情况,称赞面完就写面经非常有悟性。能不能有二面无所谓,面完能不能把自己批的体无完肤,发现问题,快速查漏补缺最重要。

确实,春招有二个多月,自己想去的大厂有几十家。哪怕挂掉其中的大多数,只要拿一家offer, 自己也是大厂打工人。学长说,这个叫复盘,他在百度做项目经常会复盘,这是成长的重要法宝。另外,从哲学角度,可以借鉴曾国藩的屡败屡战打落牙齿和血吞,又秃又强的面试官虐我再多,需咬牙成长。在每场面试中,学习成长,成功是早晚的事。

我顺便想问学长二面一般问什么,他没有直接告诉我题型或范围,说不想扼杀我自己思考及解决问题的能力。只告诉我,一面一般是用人的leader, 二面会换人面。二面面试官要不是隔壁组的leader,要不就是部门经理。二面面试官一般会根据一面面试官的反馈,做深入考查,然后反馈给一面面试官他的意见。

所以,跟学长的交流获得以下信息:

  • 根据我的反馈还有面经,一面还行(面试撑到了1小时,面试官愿意花这么多时间,说明有戏)。

  • 不停的复盘,内心的坚定是成功的关键。

  • 二面还是场恶战,要靠自己用心准备。如果二面也表现好,一二面两位面试官双双通过的话,可能就爽歪歪了。

押题

二面若有,面试官可否是周芷若?不管如何,得在这一周练练九阳神功。

手写代码、业务场景题展示编程功底。

算法,还得再巩固下。一面考dp时,比相亲还尴尬,得整点活。

VUE底层和源码,一面基础没问。好好准备下响应式、虚拟DOM、diff算法,俺也可以砍出这三板斧,拥有30分钟高质量的面试展示时间,基本可以给二面的成功定个调。

缓存和性能优化整一整,齐活...

手写代码

数组扁平化

扁平化有很多种写法,建议大家刷下JS数组专题1️⃣ ➖ 数组扁平化 - 掘金 (juejin.cn)

扁平化是一道高频手写考题,不难,实现方案多种多样,但容易流俗。面试官的考查点在于JS基础,看我们对递归、数组toString、es6等是否灵活掌握,如果能有个亮点解法就非常nice。

我打算常见方案口述,给面试官手写一个非递归实现方案,用栈。

js 复制代码
const flatter = (arr) => { 
    # 将数组展开, 只在一端操作便是栈
    let stack = [...arr]; 
    # 结果数组
    let res = []; 
    # 栈空就处理完了
    while(stack.length){ 
        # 取出最后的元素
        let num = stack.pop(); 
        # 如果是数组,继续入栈
        if(Array.isArray(num)) {
            stack.push(...num); 
        } else { 
         // 非数组,加入res
         res.push(num);
     } 
   } 
   // 反转数组 尽量还原原数组顺序
   return res.reverse(); 
}

图片懒加载

  • 为什么要做懒加载?

这个页面启动时加载了几十张图片(甚至更多),而这些图片请求几乎是并发的,在 Chrome 浏览器,最多支持的并发请求次数是有限的,其他的请求会推入到队列中等待或者停滞不前,直到上轮请求完成后新的请求才会发出。所以相当一部分图片资源请求是需要排队等待时间的。

在上面可以看出,有部分图片达到几百 kB,甚至1.5M,直接导致了加载时间过长。

优化页面加载速度非常关键, 懒加载必须做

  • 图片懒加载的原理

<img src="./img/default.png" data-src="./img/1.jpg" /> 真实地址放在data-src中,这样就不会触发请求。

  • getBoundingClientRect

实现懒加载的核心API,就是DOM元素(img)上的getBoundingClientRect方法。

getBoundingClientRect方法返回元素相对视口的位置,用于判断图片是否进入可视区域。当图片距离视口的距离小于视口高度时,加载该图片。

javascript 复制代码
function lazyload() {
  let viewHeight = document.body.clientHeight //获取可视区高度
  let imgs = document.querySelectorAll('img[data-src]')
  imgs.forEach((item, index) => {
    if (item.dataset.src === '') return

    // 用于获得页面中某个元素的左,上,右和下分别相对浏览器视窗的位置
    let rect = item.getBoundingClientRect()
    if (rect.bottom >= 0 && rect.top < viewHeight) {
      item.src = item.dataset.src
      item.removeAttribute('data-src')
    }
  })
}
  • 事件绑定
javascript 复制代码
window.addEventListener('scroll', lazyload)
  • 节流

scroll 事件会在很短的时间内触发很多次,严重影响页面性能,为了提高网页性能,我们需要一个节流函数来控制函数的多次触发,在一段时间内(如 200ms)只执行一次回调。

throttle 这里就不写了。

javascript 复制代码
window.addEventListener('scroll', throttle(lazyload, 200))
  • IntersectionObserver

IntersectionObserver 是一个新的 API,可以自动"观察"元素是否可见。不需要计算这些属性的方式,也不需要搞节流。如果大家了解观察模式就应该好理解。

lua 复制代码
var io = new IntersectionObserver(callback, option) // 开始观察 io.observe(document.getElementById('example')) // 停止观察 io.unobserve(element) // 关闭观察器 
io.disconnect()

现在我们用IntersectionObserver实现图片懒加载

entry.isIntersecting 可以直接判断是否进入可视区

js 复制代码
const imgs = document.querySelectorAll('img[data-src]')
const config = {
  rootMargin: '0px',
  threshold: 0,
}
let observer = new IntersectionObserver((entries, self) => {
  entries.forEach((entry) => {
    if (entry.isIntersecting) {
      let img = entry.target
      let src = img.dataset.src
      if (src) {
        img.src = src
        img.removeAttribute('data-src')
      }
      // 解除观察
      self.unobserve(entry.target)
    }
  })
}, config)

imgs.forEach((image) => {
  observer.observe(image)
})
  • vue 可以通过编写懒加载指令实现懒加载
ini 复制代码
<img v-lazy="xxx.jpg" />

下次新开一篇写下v-lazy指令,这里就不展开了。

到这里,懒加载回答的比较完美了。

算法

面试:百度一面,吓尿了 - 掘金 (juejin.cn)一文发表后,蛮多友友表示有同感。其中上图大佬,三战百度,两次都被问了斐波那契数列。看来面试前狂刷算法高频考题的确有必要,只要有颗想去百度的心,被虐8次也无所谓。 三战百度, 我吓尿了....

背包问题 (Knapsack Problem)

背包问题是很多困难动态规划题的母题,虽然之前刷过,我还是准备再复习一遍。

背包问题分为01背包、完全背包、多重背包、分组背包、混合背包等,到不是我想一次性把这些背包葫芦娃兄弟搞定,说实话短时间内也是不可能的。因为01背包问题比较简单,我就想怎么在这个问题上表现的更深入一些,跟其它竞争者有些区别。所以我在准备这道题的时候,刻意的准备了点相关背包的问题。

  • 01 背包是基础,牢牢把握。

有 n 件物品,物品重量用一个名为 weights 的数组存起来,物品的价值用一个名为 values 的数组存起来;每件物品的重量用 weights[i] 来表示,每件物品的价值用 values[i] 来表示。现在有一个容纳重量上限为 bagWeight 的背包,问你如何选取物品放入背包,才能使得背包内的物品总价值最大?

物品 重量 价值
物品0 1 15
物品1 3 20
物品2 4 30

这道题并不难,首先我们要讲清楚01背包的概念(面试不要慌,表达清楚)。01的意思是拿或不拿,背包有容量上限,如何取物品,得最值,适合用动态规划来解决。

不急于使出马保国老师的"接、化、发",一是撑撑有效面试时长、二是与别的面试者区分开来(其它人一上去肯定就dp公式一写,当了把一分钟解决问题的快男)。我打算先聊聊这题的暴力破解法,把背包问题聊清楚。

暴力解法

每个物品只有两个状态:取与不取。n 个物品组成最大价值,那么时间复杂度是2^n。可见,指数级别的时间复杂度是相当恐怖的,上动态规划。

动态规划

上动规五部曲!

  • 明确dp数组的定义

    背包问题可以用二维数组(重量和价值两个维度),但其实可以优化为一维, 这也会是面试官感兴趣的地方,安排!但这里我们先搞二维的。

    dp[i][j] i 表示什么意思?表示[0-i]的物品,即物品的编号。

    j表示容量,背包的容量是一边取物品,一边增加的,最后不超过c就可以了。

    dp[i][j]表示从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少

    我们围绕数组的意义去执行初始化、递推公式等...

  • 递推公式

    dp[i][j] 取决于第i个物品放不放进背包。

    不放物品i, 背包容量为j(细节,dp数组的意义)它所能装的最大价值是dp[i-1][j]。在这种情况下,i-1表示不放物品i, j的意思是价值还是不放前的价值。

    放物品i,背包的最大价值应该是背包容量-物品重量的最大价值+物品i的价值 dp[i-1][j-weights[i]] + values[i]。这里有点复杂哈。dp[i-1][j-w[i]]表示不放物品i时,重量当然的j-weights[i]的最大价值 加上第i件物品的价值。

    那么公式就出来了,取他们的最大值:

    js 复制代码
    dp[i][j] = Math.max(dp[i-1][j], dp[i-1][j-weights[i]]+values[i])
  • 初始化dp数组

初始化,一定要和dp数组的定义吻合。所以二维表格中一列为背包重量,一列为物品价值, 格子中的值为最大价值。

状态转移方程dp[i][j] = Math.max(dp[i-1][j], dp[i-1][j-weights[i]]+values[i])中可以看出,dp[i][j]由它的上方(dp[i-1][j])和左上方( dp[i-1][j-weights[i]])推导而来,所以初始化时,我们要考虑第一行和第一列。

首先从dp[i][j]的定义出发,如果背包重量j为0的话,无论怎么选物品,背包价值总和一定为O。

js 复制代码
for (let i = 0; i < n;i++) {
    dp[i][0] = 0;
}

再从 dp[i][j] = Math.max(dp[i-1][j], dp[i-1][j-weight[i]]+value[i])出发,可以看出i 是由 i-1 推导出来,那么i为0的时候就一定要初始化。即dp[0][j]。

js 复制代码
for (let j = weight[0]; j <= bagWeight; j++) {
    dp[0][j] = value[0];
}

其他下标怎么初始化呢?因为每个格子放的是最大价值, 等待被指导。而且物品的价值为正,所以只需要初始化为0就可以了。

所以, 最终初始化代码如下:

js 复制代码
function knapsack(bagWeight, weights, values) {
    let n = weights.length;
    // 整个表格都为0
    let dp = new Array(n + 1).fill(0).map(() => new Array(bagWeight + 1).fill(0));
    
    for (let j = weights[0]; j <= bagWeight; j++) {
        dp[0][j] = values[0];
    }
    console.log(dp)
}

knapsack(4, [1, 3, 4], [15, 10, 30]);

打印结果

  • 遍历

这里有两重遍历的维度,即物品背包重量

那么到底是先遍历物品还是先遍历重量呢?

我们先来遍历物品

ini 复制代码
for(let i = 1; i < n; i++) { 
// 遍历物品 从1 开始, 是因为初始化时搞了
    for(let j = 0; j <= bagWeight; j++) { 
    // 遍历背包容量
        if (j < weights[i]) dp[i][j] = dp[i - 1][j]; // 这个是为了展现dp数组里元素的变化
        else dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - weighst[i]] + values[i]);
    }
}
  • 打印DP数组
ini 复制代码
return dp[n-1][bagWeight];

完整代码如下:

ini 复制代码
function knapsack(bagWeight, weights, values) {
    let n = weights.length;
    let dp = new Array(n).fill(0).map(() => new Array(bagWeight + 1).fill(0));

    for (let j = weights[0]; j <= bagWeight; j++) {
        dp[0][j] = values[0];
    }
    // console.log(dp)

    for(let i = 1; i < n; i++) { 
        // 遍历物品 从1 开始, 是因为初始化时搞了
            for(let j = 0; j <= bagWeight; j++) { 
            // 遍历背包容量
                if (j < weights[i]) dp[i][j] = dp[i - 1][j]; // 这个是为了展现dp数组里元素的变化
                else dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - weights[i]] + values[i]);
            }
        }
    console.log(dp)
    return dp[n-1][bagWeight]
}

console.log(knapsack(4, [1, 3, 4], [15, 10, 30]));

滚动数组

刚刚我们用二维数组实现了01背包,现在让我们来降维。面试官看我上面的二维解法,大概率会问能不能优化一下,一维数组能不能解决。二维数组卖个破绽,其实一维解决早已准备。勾引面试官往我们准备好的包围圈跳。这里借用郭德纲的一句话,"观众什么时候笑,哪个包袱什么时候响,都是我提前安排好的"。只不过, 这里的观众就是面试官。就伺候他一个人, 那还不准备点包袱

把二维数组降到一维,说明有些状态是可以压缩的。

问题还是这个问题

物品 重量 价值
物品0 1 15
物品1 3 20
物品2 4 30

背包最大重量为4, 最大价值为30

状态转移方程(递推公式) dp[i][j] = Math.max(dp[i-1][j], dp[i-1][j-weights[i]]+values[i])

i 表示[0-i]个任意物品, j 表示 0-bagWeight 背包重量 dp[i][j] 最大价值。

由递推公式可以看出,dp[i]这一层(不同j)是由上一层(i-1)推导出来的。因为我们最后只需要最大价值,并不需要把每一步都留下来。所以,我们可以直接将上一层的数据拷贝出来,直接计算当前层,将原层值覆盖。这样我们只需要一层,二维变一维。 它可以将一个矩阵压缩成一行, 每一次计算都更新这一行数据。看起来就像是在滚动,这就叫滚动数组

再次使用动规五规五部曲

  • 明确dp数组的定义

dp[j] j还是之前的j , 用了滚动数组,不需要i了。容量为j的背包所背最大价格的物品

  • 递推公式

    不放物品i 还是dp[j] 放物品i dp[j-weights[i]] + values[i]

    dp[j] = Math.max(dp[j], dp[j-weights[i]] + values[i])

  • 初始化

递推公式好理解,我们看下初始化会有什么变化呢?

dp[0] 按含义为0

那么其它项初始化为啥呢? 这里都初始化为0就好,因为dp[j]能放下的最大价值都大于0

  • 遍历顺序
ini 复制代码
for(let i = 0; i < n ; i++) { // 遍历物品  
    for(let j = bagWeight; j >= weight[i]; j--) { // 遍历背包容量  
        dp[j] = Math.max(dp[j], dp[j - weights[i]] + values[i]); 
    }  
}
  • 代码
ini 复制代码
function knapsack( bagWeight, weights, values) { 
// dp是动态规划的状态保存数组 
    const dp = new Array(bagWeight+1)).fill(0) 
    let n = weights.length
    // res 用来记录所有组合方案中的最大值
    let res = -Infinity
    for(let i=1;i<=n;i++) { 
        for(let v=baseWeight;v>=weights[i];v--) { 
        // 写出状态转移方程 
            dp[v] = Math.max(dp[v], dp[v-weights[i]] + values[i]) 
            // 即时更新最大值 
            if(dp[v] > res) { res = dp[v] } 
        } 
    } 
    return res 
}

其它部分,明天开第二篇吧。没有大厂面试烦,这面完一面后的等待啊,更是心烦...

总结

  • 手写题要刻意练习

  • 动态规划不能凭感觉来,要搞清楚dp定义、推导公式、初始化、遍历顺序、打印DP数组五步每步都搞扎实。

  • 除了状态转移方程,初始化和遍历也可能比较复杂

  • 面试中的亮点和技能包袱,都是可以提前安排好的。 比如01背包,我就从二维引到一维,逐步展示我对动态规划的理解,主导面试节奏。

参考资料

相关推荐
前端啊龙2 分钟前
用vue3封装丶高仿element-plus里面的日期联级选择器,日期选择器
前端·javascript·vue.js
一颗松鼠6 分钟前
JavaScript 闭包是什么?简单到看完就理解!
开发语言·前端·javascript·ecmascript
马剑威(威哥爱编程)23 分钟前
MongoDB面试专题33道解析
数据库·mongodb·面试
小远yyds26 分钟前
前端Web用户 token 持久化
开发语言·前端·javascript·vue.js
吕彬-前端1 小时前
使用vite+react+ts+Ant Design开发后台管理项目(五)
前端·javascript·react.js
学前端的小朱1 小时前
Redux的简介及其在React中的应用
前端·javascript·react.js·redux·store
guai_guai_guai2 小时前
uniapp
前端·javascript·vue.js·uni-app
bysking2 小时前
【前端-组件】定义行分组的表格表单实现-bysking
前端·react.js
独行soc3 小时前
#渗透测试#SRC漏洞挖掘#深入挖掘XSS漏洞02之测试流程
web安全·面试·渗透测试·xss·漏洞挖掘·1024程序员节
王哲晓3 小时前
第三十章 章节练习商品列表组件封装
前端·javascript·vue.js