如果你也在准备春招,欢迎加我微信
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件物品的价值。那么公式就出来了,取他们的最大值:
jsdp[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背包,我就从二维引到一维,逐步展示我对动态规划的理解,主导面试节奏。