前言
在百度的学长,帮我内推了下简历。高兴还没有两秒,就接到通知面试的电话,瞬间压力山大。小公司也没怎么面,八股文背的也不利索,看到镜头里又秃又强的面试官的那一记得,有如看恐怖片吓尿了。
百度一面
1. 宇宙铁律,介绍下自己
还好,之前看到过敖丙大佬面试自我介绍5句话公式 - 掘金 (juejin.cn)一文,里面有个公式:我是谁+从哪里来+我做过什么+有什么成绩+为什么能胜任。
面试官您好,我叫梦梦,东华理工大学25届软件专业大三在读,想来百度前端岗位实习,要是是AIGC项目就更好了。低年级的时候,跟着学长一起在B站学习、打些比赛,拿过蓝桥杯省二,奖学金也拿了一些。个人比较喜欢前端,也能用node写后端。chatgpt火了后,又对AIGC非常感兴趣,所以学习了python、prompt enginneering、吴恩达AI课程等,平时写代码会用一些AI Copilot工具提升编程效率。
仔细读过《你不知道的JavaScript》,并在掘金上写过JS基础和底层相关系列文章,热爱技术分享,掘金优秀作者。平时主要使用vue,熟悉全家桶开发,数据流按理及后台管理系统开发。阅读过vue、axios、ElementPlus的源码,未来会持续学习。
未来最大的机会是AI的机会, 非常期待在AIGC产品或项目写下自己的代码。同时非常看好百度代表中国队参与AI技术革命,为用户开发划时代的产品与体验。未来几年,我将为此而奋斗, 谢谢。
2. promise有几种状态,可以重复改变吗?
Promise对象有三种状态
Pending(未决)初始状态
Fulfilled(已履行/成功):操作成功完成时的状态
Rejected(已拒绝/失败):操作因错误或异常未能完成时的状态
Promise状态变化的特性是:
Promise状态的转变是不可逆且只能发生一次。也就是说,一个Promise不能从
Fulfilled
状态变回Pending
状态,也不能从Rejected
状态变为Pending
或者Fulfilled
状态。 一旦Promise从Pending
状态变为Fulfilled
(resolved)或Rejected
(rejected),它就永远不会再改变。因此,Promise的状态不能重复改变。
感觉回答的还可以,记得去字节的会长提醒面试时,切忌一个字一个字的蹦面试官。面试不只是回答问题,而是要展示自己
。抓住熟悉的知识或技能点,就来个滔滔不绝,面试官不喊停就一直说
。于是想到下面这个API:
Promise.resolve()与Promise.reject() 用于创建已确定状态的Promise对象,方便快速返回成功的或失败的结果
面试官一听这个就感兴趣了, 继续提问Promise还提供了哪些静态方法,平时怎么用的?开心, 面试官进包围圈了。
- Promise.all(iterable)
参数是promise对象数组。只有当所有Promise都变为fulfilled时,返回的Promise才会变为fulfilled,并且结果是一个包含所有Promise结果的数组;只要有一个Promise变为rejected,则整体Promise也会立即变为rejected,返回第一个rejected Promise的理由。
- Promise.race(iterable)
在传入的 Promise 数组中任何一个 Promise 解决(resolve)或拒绝(reject)时,会立即以那个率先改变状态的 Promise 的结果为准来解决或拒绝。
这里强调下细节,其它的promise实例仍然会继续运行,只不过其状态和结果不会归于最终的结果。
Promise.race
关注的是速度最快的 Promise 的结果,而 Promise.all
关注的是所有 Promise 是否都成功完成。
- Promise.allSettled(iterable)
和Promise.all()
相似,它等待所有Promise都达到settled状态(即无论是fulfilled还是rejected)。一旦所有Promise都决断了,返回的Promise会变成fulfilled,并且结果是一个数组,包含了每个输入Promise的结果描述对象,这些对象具有status
('fulfilled'或'rejected')和对应的value
或reason
属性。
Promise.all()
更关注所有 Promise 是否都成功完成,它适用于需要所有任务成功完成才能继续下一步场景。而 Promise.allSettled()
则允许你观察一组异步操作的所有结果,无论成功与否,这对于获取并处理所有任务的最终状态非常有用。
到此,在三种状态的语法回答外,还将并发任务及sellted等偏业余的需求表演给面试官,看表情他还是挺满意的... 如果面试官不打断,我准备继续聊异步、红绿灯、手写promise... 面试就要变被动为主动,将自己擅长的表演出来。
3. 输出结果,为什么?
js
const obj3 = {a: 1};
const obj4 = {b: 2};
console.log(obj3 == obj4); // false
console.log(obj3 === obj4); // false
结果:
js
false,false
原因:
==
值相等,===
严格相等,即值和类型都相等- 类型转化,虽然
obj3
和obj4
值不一样,但面试官要听的是我们对于JS类型转换的理解。
javascript
在转换不同的数据类型时,相等和不相等操作符遵循下列基本规则:
如果由一个操作数是布尔值,则在比较相等性之前先将其转换为数值---false转换为0,而true转换为1;
如果一个操作数是字符串,另一个操作数是数值,在比较相等性之前先将字符串转换为数值;
如果一个操作数是对象,另一个操作数不是,则调用对象的valueOf()方法,用得到的基本类型值按照前面的规则进行比较;
这两个操作符在进行比较时则要遵循下列规则。
null和undefined是相等的。
要比较相等性之前,不能将null和undefined转换成其他任何值。
如果有一个操作数是NaN,则相等操作符返回false,而不相等操作符则返回true。重要提示:即使两个操作数都是NaN,相等操作符也返回false;因为按照规则,NaN不等于NaN。
如果两个操作数都是对象,则比较他们是不是同一个对象。如果两个操作数都是指向同一个对象,则相等放回true,否则返回false。
所以这里不会转换两个对象,而是比较他们是不是指向同一个地址,是否是同一个对象。
===
严格相等,这里比较的也是引用地址。
所以,在比较两个对象时,并不会发生类型转换以试图使它们相等。相等性判断直接基于对象的内存地址。
4. leetcode 原题编辑距离
js
给你两个单词 `word1` 和 `word2`, 请返回将 `word1` 转换成 `word2` 所使用的最少操作数。
你可以对一个单词进行如下三种操作:
- 插入一个字符
- 删除一个字符
- 替换一个字符
首先,这道动态规划题有些复杂,大家需要刷一些基础的动态规划题再来搞这道。像百度等这种级别的大厂,算法题我称为
三板斧
。一般是1-2道简单或中等题,再是1-2道有难度的动态规划题。只要我们准备好动态规划由简到难的各种常考题型,扛住面试官的前三板斧子,基本没有问题。
- 举例
js
word1 horse
word2 ros
1. word1 变
horse -> rorse h->r 替换
rorse -> rose 删除最2个r
rose-> ros 删除最后e
操作次数是3次 由word1 变成word2
再换个思考 由word2变成word1也是可以的
2. word2 变
ros -> rose 添加e
rose->rorse 添加r
rorse-> horse r 替换为h
其实就是上面的逆向操作
3. 都变
horse -> rorse h-> r word1 变
rorse -> rose 删除r word1 变
ros -> rose word加e word2 变
word1 和word2 都操作了, 一共操作了3次
分析demo,方便等下我们考虑最优子结构的各种情况
天啊, 太复杂了,替换、删除、添加三种操作、两个单词混合修改。别怕,最值问题求解,就用动态规划。看上去复杂的dp
问题,使用动规五部曲
就能化腐朽为神奇。
动规五部曲
-
定义dp数组 比较两个字符串,那么我们要定义一个二维的dp[i][j],两个字符串的最少操作次数(无论哪个操作字符,哪种操作,都可以用二维矩阵涵盖)
以i-1结尾的word1 ,以j-1结尾的word2
dp[i][j]就是让两个字符相同的最少操作次数,根据动态规划的局部最优亦是全局最优,最后的dp[m][n]就是我们要的结果
-
递推公式
我们要比较两个字符串,就要比较每个元素,那么要比较哪些元素呢?
word1[i-1]、word2[j-1] 递推,自顶向下思考
js
// 如果两个字符相同
if (word1[i-1] == word2[j-1]) {
// 不需要添加、删除、替换元素
// 最少操作次数可以是不改变的,取上一次的最少操作数,因为dp局部最优也是全局最优的
dp[i][j] = dp[i-1][j-1]
} else {
// 上操作
// dp[i][j] = dp[i - 1][j - 1] + 1, // 替换操作
// dp[i][j] = dp[i][j - 1] + 1, // 插入操作
//dp[i - 1][j] + 1)// 删除操作
dp[i][j] = Math.min(
dp[i - 1][j - 1] + 1, // 替换操作
Math.min(dp[i][j - 1] + 1, // 插入操作
dp[i - 1][j] + 1) // 删除操作
);
// 总之, 根据动态规划的思想,只要有一步操作就可以到达
}
- 初始化
怎么来考虑初始化问题呢? 上图dp[i][j]会由左上角的dp[i-1]j-1、左边dp[i-1][j]、上边dp[i][j-1]三个方向迭代而来。所以在初始化的时候,我们需要把第一行dp[]和第一列都初始化,这样就可以递推出相应的值。
js
for (let i = 0; i <= m; i++) {
dp[i][0] = i;
}
for (let j = 0; j <= n; j++) {
dp[0][j] = j;
}
- 迭代
js
//自底向上, 迭代
for (let i = 1; i <= m; i++) {
for (let j = 1; j <= n; j++) {
.....
}
}
- 返回结果
最后的dp[m][n] 也就是二维矩阵的右下角。
通过动态规划五步走,代码如下:
js
/**
* @param {string} word1
* @param {string} word2
* @return {number}
*/
function minDistance(word1, word2) {
const m = word1.length;
const n = word2.length;
// 初始化一个(m+1) x (n+1)的矩阵,第一行和第一列分别表示空串到word1前i个字符、空串到word2前j个字符的距离
const dp = new Array(m + 1).fill(null).map(() => new Array(n + 1).fill(0));
// 初始化边界条件:空字符串转换成任意长度的字符串至少需要该字符串的长度次操作
for (let i = 0; i <= m; i++) {
dp[i][0] = i;
}
for (let j = 0; j <= n; j++) {
dp[0][j] = j;
}
// 动态规划填充矩阵
for (let i = 1; i <= m; i++) {
for (let j = 1; j <= n; j++) {
if (word1[i - 1] === word2[j - 1]) {
dp[i][j] = dp[i - 1][j - 1]; // 如果两个字符相等,则不需要消耗操作次数
} else {
dp[i][j] = Math.min(
dp[i - 1][j - 1] + 1, // 替换操作
Math.min(dp[i][j - 1] + 1, // 插入操作
dp[i - 1][j] + 1) // 删除操作
);
}
}
}
return dp[m][n]; // 最终答案位于dp数组右下角
}
5. 如何用AI工具 提升开发效率
- 之前申请了Github的
Copilot
,学生党免费嘛。对代码提速和源码学习都有挺大帮助的。也试过下通义千问的vscode 插件,挺好的。 - 使用各种chat bot(Chatgpt等),从前端到后端、AI学习、数据库等,提问式学习及解决问题,拥抱AI Native。
- 刻意练习一些prompt 的技巧, 生成前端页面、SQL等 比如在做后台管理系统的时候, tailwind的一些页面,基本都是chat 完成。
- 学习transfromer、openai 等AIGC类技能,将一部分的编程任务交给Agent来完成,发挥大模型的能力。最近在学习
LangChain
, 对AI很感兴趣。
总结
百度的面试还是比较专业的,能学到很多东西。对于刚准备春招的我,真是有点吓尿了。不管结果怎么样, 继续前行吧,要去亲戚家拜年了, 先写到这,下一篇继续聊....
参考资料
假如您也和我一样,在准备春招。欢迎加我微信shunwuyu,这里有几十位一心去大厂的友友可以相互鼓励,分享信息,模拟面试,共读源码,齐刷算法,手撕面经。来吧,友友们!