题目:给定两棵二叉树,这两棵树的部分节点是重叠的,部分没重叠。 现在要将它们合并到一个新的二叉树中。合并规则是,如果两个节点重叠,则将两个节点的值相加,作为合并节点的新值;否则,非空节点的值作为新树的节点。
例如:
/*
采用递归解决
t1 t2 都不空,则创建新节点,值为两者之和,
他的左右子结点分别递归调用,
若一者为空,则直接将此节点合并到新数上面。
*/
var mergeTrees = function (t1, t2) {
let node;
if (t1 && t2) {
// 都不为空节点,则创建新节点,并递归左右节点
node = new TreeNode(t1.val + t2.val);
node.left = mergeTrees(t1.left, t2.left);
node.right = mergeTrees(t1.right, t2.right);
} else if (t1 && !t2) {
// t2为空,则返回t1节点
node = t1
} else if (!t1 && t2) {
// t1为空,则返回t2节点
node = t2
} else {
// 均为空,则返回null
return null;
}
return node;
};
复制代码
2.子串问题
题目:给定两个字符串s1z和s2,如果s2包含s1的任一排列,则返回true。换句话说,第一个字符串的排列之一,是第二个字符串的子串。
例1:
Input: s1 = “ab”, s2 = “eidbaooo”
Output: True
说明: s2包含s1的一个排列(“ba”)
例2:
Input: s1= “ab”, s2 = “eidboaoo”
Output: False
/**
- @param {string} s1
- @param {string} s2
- @return {boolean}
*/
// 当两个字符串包含具有相同频率的相同字符时,一个字符串才是另一个字符串的排列
var checkInclusions = function (s1, s2) {
// 判断长度
if (s1.length > s2.length) {
return false
}
// 建立数组存放频率
let count = new Array(26).fill(0);
// 检测是否命中,全为0则命中
const isAllZero = (count) => count.every(val => val === 0);
// 初始化窗口数据
for (let i = 0; i < s1.length; i++) {
count[s1[i].charCodeAt(0) - 97]++;
count[s2[i].charCodeAt(0) - 97]–;
}
// 判断第一个窗口数据是否命中
if (isAllZero(count)) return true;
// 滑动窗口,每次向右滑动一个
for (let i = s1.length; i < s2.length; i++) {
// 将窗口右边第一个数据作为频率放到数组里
count[s2[i].charCodeAt(0) - 97]–;
// 将窗口左边对应的频率删除掉
count[s2[i - s1.length].charCodeAt(0) - 97]++;
// 检测是否命中
if (isAllZero(count)) return true;
}
return false;
};
复制代码
用滑动窗口算法就可实现
3.奇偶链表
给定一个单向链表,将所有奇数节点组合在一起,然后是偶数节点。需要注意的是,这里指的是节点序号而不是节点中的值。 例:
输入1-> 2-> 3-> 4-> 5-> NULL,
输出1-> 3-> 5-> 2-> 4-> NULL。
注意:
1)偶数和奇数组内的相对顺序应与输入时保持相同。
2)第1个节点是奇数,第2个节点是偶数,以此类推
/**
1、遍历标记链表每个节点的奇偶
2、保存两个变量
lastOdd 上一个奇节点
lastNode 上一个节点
当遍历到奇节点时,将上一个奇节点lastOdd和当前节点连接起来即可
/
/*
- @param {ListNode} head
- @return {ListNode}
*/
var oddEvenList = function(head) {
let node = head;
let isOdd = true;
while(node){
// 遍历标记链表每个节点的奇偶 第一个为奇
node.tag = isOdd;
node = node.next;
isOdd = !isOdd;
}
// lastOdd 上一个奇节点
let lastOdd = head;
// lastNode 上一个节点
let lastNode = null;
node = head;
while(node){
// 当遍历到奇节点时,将上一个奇节点和当前节点连接起来
if(node.tag && lastNode!==null){
lastNode.next = node.next;
node.next = lastOdd.next;
lastOdd.next = node;
lastOdd = node;
}
lastNode = node;
node = node.next;
}
return head
};
复制代码
4.解析url
题目:页面page的主函数入口在page.js,以下为其依赖树。
为了性能要求,我们页面会组装一个combo请求: res.wx.qq.com/F.js,E.js,D… 请设计genUrl(requireTree),输出如上所示的combo url。requireTree的数据结构如下页。
const requireTreeData = {
“name”: “page.js”,
“require”: [
{
“name”: “A.js”,
“require”: [
{
“name”: “C.js”,
“require”: [
{
“name”: “F.js”
}
]
}
]
},
{
“name”: “B.js”,
“require”: [
{
“name”: “D.js”,
“require”: [
{
“name”: “F.js”
}
]
},
{
“name”: “E.js”,
“require”: []
}
]
},
]
};
复制代码
这道题类似二叉树的广度优先遍历:
/**
- @param {object} requireTree
- @return {string}
*/
var genUrl = function (requireTree) {
let data = [];
let nodeStack = [requireTree];
let set = new Set();
while (nodeStack.length > 0) {
let rowLength = nodeStack.length;
let rowData = [];
for (let i = 0; rowLength > i; i++) {
let nowNode = nodeStack.shift();
if (set.has(nowNode.name)) {
break;
}
rowData.push(nowNode.name);
if (nowNode.hasOwnProperty(‘require’) && nowNode.require.length > 0) {
nowNode.require.forEach((item)=>{
nodeStack.push(item)
})
}
set.add(nowNode.name);
}
data.push(…rowData);
}
data = data.reverse().join(’,’);
returnhttp://res.wx.qq.com/${data}
;
};
复制代码
一面(50多分钟)
聊了聊笔试的算法题
var let const 的区别,如何用 es5 实现 let const
const : Object.defineProperty
Object.defineProperty(window, “A”, {
value: 1, // 值
writable: false, // 设置属性只读
configurable: true, // 是否可删除
enumerable: true // 是否可枚举
});
复制代码
let :立即执行函数
(function(){
var a = 1;
console.log(a);
})();
复制代码
class 如何用 es5 实现
// class实现
class AClass {
constructor(name) {
this.name = name;
}
static StaticName = 'dell';
getName() {
return 'dellyoung';
}
static StaticGetName() {
return 'delldell'
}
}
// es5实现
var A = function A(name) {
this.name = name;
};
A.prototype.getName = function () {
return ‘dellyoung’;
};
A.StaticName = ‘dell’;
A.StaticGetName = function () {
return ‘delldell’
}
复制代码
箭头函数和普通函数的区别
箭头函数不用绑定 this ,会捕获其上下文的 this 值作为自己的 this ,用 call、apply 不能改变 this
箭头函数没有原型,是匿名函数,不能作为构造函数也就不能使用new
箭头函数不绑定 arguments ,可以用 …arg 解决
什么是跨域,如何解决跨域问题
协议、域名、端口号 之一不同就是跨域
可以用 jsonp 、 CORS 解决跨域问题,当然特殊场景可以用 document.domain 、 window.postMessage 、 WebSocket
讲解一下jsonp如何用,最简单的版本如下:
复制代码
安全问题有了解吗,讲讲cookie安全、XSS攻击、CSRF攻击 ,以及分别如何防范
cookie安全:
1、HttpOnly:使用 HttpOnly 标记的 Cookie 只能使用在 HTTP 请求过程中,所以无法通过 JavaScript 来读取这段 Cookie
2、SameSite:设置SameSite,使cookie不会随着跨域请求发送,缺点兼容性差(Chrome兼容)三个值:
Strict 最为严格:浏览器会完全禁止第三方Cookie
Lax 相对宽松一点:允许 Get 方式的表单提交或a标签携带Cookie None 不设置:是否跨域都会发送Cookie
XSS攻击、CSRF攻击
讲一下HTTPS的握手过程
开始问简历上的项目,面试官对项目很感兴趣,讲了很久。
有什么问题想问我的吗?
二面(40多分钟)
请问下面代码打印结果是?(异步、作用域、闭包相关)
for (var i = 0; i < 4; i++) {
setTimeout(function() {
console.log(i);
}, 300);
}
复制代码
结果:4 4 4 4
追问1: 解释一下为什么输出 4 个 4
每一次 for 循环的时候, setTimeOut 都执行一次,但是里面的函数没有被执行,而是被放到了任务队列里面,等待执行, setTimeOut 是延时异步执行的,只有当主线程上的任务执行完成,主线程时,才会执行任务队列的任务,所以当主线程 for 循环执行完之后 i 的值为4,这个时候再去任务队列中执行任务, i 全部为4.
追问2: setTimeOut 在浏览器上执行的原理知道吗,讲一下
每次执行到 setTimeOut ,会创建一个延时任务,并将该任务放到浏览器渲染进程的 延时任务队列 中,一个延时任务通常包含:回调函数、发起时间、延迟执行时间。当主线程上的任务执行完毕,主线程空闲时,主线程会对延迟任务队列中的任务,通过发起时间、延迟执行时间计算出已经到期的任务,然后依次执行这些到期的任务,等到期的任务全部执行完,主线程就进入到下一次循环。
正好之前画过一个示意图,我把它放这
追问3: 如果我想要输出0 1 2 3,怎么改
把 var 改成 let
追问4: 说说原因
通过 var 定义的变量是无法传入到这个函数作用域中的,通过使用 let 来声明块变量,这时候变量就能作用于这个块,所以 function 就能使用 i 这个变量了;
追问5: 我想要输出0 1 2 3,还有其他方法吗
可以使用立即执行函数
for (var i = 0; i < 4; i++) {
(function (num) {
setTimeout(function() {
console.log(num);
}, 300);
})(i)
}
复制代码
追问6: 解释一下
立即执行函数会创建一个独立的作用域,这个作用域里面的变量,外面访问不到。这样每次for循环传入参数 i ,每次都创建了一个独立的作用域,4次就创建了4个作用域,参数分别为 0 1 2 3。
一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法(递归相关)
function flag(n, map = new Map()) {
if (n < 1) {
return 0;
}
if (n === 1) {
return 1;
}
if (n === 2) {
return 2;
}
if (map.has(n)) {
return map.get(n);
}
const value = flag(n - 1) + flag(n - 2);
map.set(n, value);
return value;
}
复制代码
追问1: 还有别的方法嘛?
循环
function flag(n) {
if (n < 1) {
return 0;
}
if (n === 1) {
return 1;
}
if (n === 2) {
return 2;
}
let a = 1;
let b = 2;
let sum = a + b;
for (let i = 3; i <= n; i++) {
sum = a + b;
a = b;
b = sum;
}
return sum;
}
复制代码
追问2: 现在的解题思路 时间复杂度是?
O(n)
如下代码打印结果是?(链式调用相关)
function test(a,b) {
console.log(b)
return {
test:function©{
return test(c,a);
}
};
}s
var retA = test(0); retA.test(2); retA.test(4); retA.test(8);
var retB = test(0).test(2).test(4).test(8);
var retC = test(‘good’).test(‘bad’); retC.test(‘good’); retC.test(‘bad’);
复制代码
结果:
undefined 0 0 0
undefined 0 2 4
undefined good bad bad
复制代码
这个函数就是实现了打印上一个输入变量的功能,明白这个就好答了
讲讲学习历程吧
从大一进入技术社团入门web编程开始讲起,中间各种比赛和项目以及实习历程,再讲到至今大三做过的 30+ 项目。然后期间面试官提出了很多问题,比如关于对项目的优化,关于对项目的思考等等。
三面(40多分钟)
两数之和 给定一个整数数组 nums 和一个目标值target,请你在该数组中找出和为目标值的那两个整数,并返回他们的数组下标。你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。
示例:
给定 nums = [2, 7, 11, 15], target = 9
因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]
var twoSum = function (nums, target) {
let map = new Map();
for (let item of nums) {
if (map.has(item)) {
return [target - item, item];
}
map.set(target - item, item);
}
return null;
};
复制代码
追问1: 目前时间复杂度和空间复杂度分别是多少?
O(n)、O(n)
追问2: 如果输入数组有序,可以有什么优化策略吗?
可以用双索引法,降低空间复杂度。
setTimeOut运行机制
正好二面有问过
内存泄露
追问1: 什么是内存泄露
由于疏忽或错误造成程序未能释放已经不再使用的内存。
追问2: 什么情况会发生内存泄露
意外的全局变量、console.log、闭包、DOM泄漏 等
追问3: 如何排查内存泄露
使用Google Chrome浏览器提供的 Heap Profiling。Heap Profiling可以记录当前的堆内存(heap)快照,并生成对象的描述文件,该描述文件给出了当时JS运行所用到的所有对象,以及这些对象所占用的内存大小、引用的层级关系等等。这些描述文件为内存泄漏的排查提供了非常有用的信息。
描述一个CSRF攻击的过程
诱导用户进入黑客的网站,该黑客网站设置Domain和Path为被攻击的域名,然后就可以拿到该域名下的cookie,通过将图片设置成被攻击域名的地址,发送请求,完成攻击。 (注意请求可以发出去,跨域只是让浏览器不能获取到服务器的返回内容)
追问1: 如何防御
SameSite(cookie) 设置SameSite,使cookie不会随着跨域请求发送,缺点兼容性差(Chrome兼容) 三个值: Strict 最为严格:浏览器会完全禁止第三方 Cookie Lax 相对宽松一点:允许 Get 方式的表单提交或a标签携带Cookie None 不设置:是否跨域都会发送Cookie
Referer 服务器检查Referer,验证请求的来源站点 Origin:因为Referer可以不带,所以还有个Origin属性,xhr、fetch、表单POST 请求都会带上Origin属性
CSRF Token 服务器下发一个Token,请求时带上Token,服务器进行验证 (表单中:可以作为value存放在一个隐藏input标签)
追问1: CSRF Token能被黑客获取到吗?
不能,浏览器可以把服务器下发到Token缓存到内存中,使得第三方网站不能获取到
问项目
面试官依然对项目很感兴趣,重点讨论了项目中的性能优化部分和组件封装
有什么需要补充的吗
聊了聊我基于 领域驱动设计(Domain-Driven Design) 思想所实践的前端架构
小结
整体面试体验很好,面试官也非常友好(等HR面中…)。
算法题比较简单,面试官是比较注重基础的,会深挖基础。
当基础讲完后,有做过几个出彩的项目也是非常重要的,特别是在项目中进行一些性能优化,面试官会非常感兴趣,要尽量将项目中的这些优点暴露出来。另外实习的期间我也主导做过一个中后台业务组件库,提高了公司前端开发的效率,面试官也很感兴趣。
有想了解更多的朋友可以去https://www.jianshu.com/p/86bb74768d12 看一下。