一面(独立闭环业务)
侧重计算机基础,问了很多OS和网络,本以为没过的收到了二面。
PS. 一面是独立闭环业务,二面变成了游戏,难道是一面挂了二面被捞么???
OS
进程线程的区别、并行并发区别、进程通信和同步(只记得信号量和管道)
网络
简述七层模型、传输层协议、TCP为什么要三次握手(这个答了洪泛攻击,实际上问的是由于网络拥塞或延迟,第三次握手的包重传且正常关闭连接后,收到了迟到的第三次握手的ACK包)、接着问洪泛攻击预防限制指定时间内连接请求次数、SYN缓存(收到SYN时仅仅分配有限空间)、SYN cookie(收到SYN时不分配任何资源,根据四元组精心构造SYN+ACK的序号,这个序号就是SYN cookie)
JS
typeof [] // object
typeof {} // object
typeof null // object
typeof undefined // undefined
function a(){}
typeof a // function
typeof 1 // number
typeof Number // function
typeof以上代码输出,数据类型,堆栈的区别,如何详细判断引用类型,手写instanceof,原型链,闭包,快排(空间复杂度和调用栈次数说错了,是lgn
),简单四则运算的实现
堆(heap)和栈(stack)
栈(stack)会自动分配内存空间,会自动释放,容量小速度快,不灵活,结构简单。堆(heap)动态分配的内存,大小不定也不会自动释放,容量大速度慢,使用灵活,结构复杂。
在访问引用类型时,需要在栈内存中查找 对应的地址,在去堆内存中取得真正的值,访问速度自然不及栈内存
基本类型和引用类型
基本类型:简单的数据段,存放在栈内存中,占据固定大小的空间。
基本类型的变量是存放在栈区的(栈区指内存里的栈内存),栈区包括了变量的标识符和变量的值。
引用类型:指那些可能由多个值构成的对象,保存在堆内存中,包含引用类型的变量实际上保存的不是变量本身,而是指向该对象的指针
基本类型是存在栈内存中的,引用类型是存在堆内存中的,但是引用类型的引用还是存在栈内存中的。
基本数据类型包括String, Number, Boolean, Null, Undefined, Symbol(ES6), BigInt(ES10)
引用类型有 Object
引用类型有这几种:object、Array、RegExp、Date、Function、特殊的基本包装类型(String、Number、Boolean)以及单体内置对象(Global、Math)。
JS有8中数据类型:String, Number, Boolean, Null, Undefined, Object, Symbol, BigInt
传值和传址
复制引用类型,复制的其实是指针,两个变量最终指向同一个对象,即复制的是栈中的地址而不是堆中的对象。
复制基本类型的值,会创建这个值的副本。
手写instanceof
// left instanceof right
function myInstanceof(left, right) {
// 这里先用typeof来判断基础数据类型,如果是,直接返回false
if(typeof left !== 'object' || left === null) return false;
while(left.__proto__){
if(left.__proto__ === right.prototype){
return true;
}
left = left.__proto__;
}
return false;
}
闭包
var a = 0;
function foo() {
var a = 1;
function bar2() {
console.log(a);
}
bar();
bar2();
}
function bar() {
console.log(a);
}
foo(); // 0 1
bar(); // 0
四则运算
实现一个 calculate 方法,满足:接受一个入参 str , 由 0 - 9 以及 + - * / 组成,calculate最终返回表达式计算结果, eg: calculate(‘1+2*3-4’) // 3
/*+ - * / ^, !, (, ), \0*/
let op = ['=', '=', '<', '<', '<', '<', '<', '>', '>',
'=', '=', '<', '<', '<', '<', '<', '>', '>',
'>', '>', '=', '=', '<', '<', '<', '>', '>',
'>', '>', '=', '=', '<', '<', '<', '>', '>',
'>', '>', '>', '>', '=', '<', '<', '>', '>',
'>', '>', '>', '>', '>', '=', ' ', '>', '>',
'<', '<', '<', '<', '<', '<', '<', '=', ' ',
' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
'<', '<', '<', '<', '<', '<', '<', ' ', '='
]
// 9中操作符
const COUNT = 9;
// 初始化优先级矩阵
let priMatrix = new Array(COUNT).fill(0);
for (let i = 0; i < COUNT; i++) {
priMatrix[i] = op.slice(i * COUNT, (i + 1) * COUNT)
}
// 计算a2 op_top a1, 结果入栈operand
function cal(a1, a2, op_top, operand) {
switch (op_top) {
case '+':
a1 += a2;
operand.push(a1)
break;
case '-':
a1 -= a2;
operand.push(a1)
break;
case '*':
a1 *= a2;
operand.push(a1)
break;
case '/':
if (a2 === 0) {
throw new Error('divide by 0');
}
a1 /= a2;
operand.push(a1)
break;
default: break
}
}
// 直接计算中缀表达式
function calculateInffix(str) {
let len = str.length;
let op = ["+", "-", "*", "/", "^", "!", "(", ")", ""];
// // 操作符
let operator = []
// // 操作数
let operand = []
for (let i = 0; i < len; i++) {
let s = str[i]
// 操作数直接入oprand栈
if (op.indexOf(s) === -1) {
operand.push(parseInt(s));
}
else {
// 操作符oprator栈为空或栈顶为'('或当前扫描到'(', 入栈
if (operator.length === 0 || operator[operator.length - 1] === '(' || s === '(') {
operator.push(s);
}
else {
// 扫描到')', 弹出一个操作符两个操作数,将运算结果入oprand栈,直到operator栈顶为'('
if (s === ')') {
let op_top = operator.pop();
while (op_top !== '(') {
let a2 = operand.pop();
let a1 = operand.pop();
// to continue
cal(a1, a2, op_top, operand);
op_top = operator.pop();
}
}
else {
let cur = op.indexOf(s), top = op.indexOf(operator[operator.length - 1]);
if (priMatrix[cur][top] === '>') {
operator.push(s);
}
else {
let op_top = operator.pop()
let a2 = operand.pop();
let a1 = operand.pop();
// to continue
cal(a1, a2, op_top, operand);
i--;
}
}
}
}
}
while (operator.length !== 0) {
let s = operator.pop()
let a2 = operand.pop();
let a1 = operand.pop();
// to continue
cal(a1, a2, s, operand);
}
return operand.pop();
}
// 中缀转后缀
function toSuffix(str) {
let len = str.length;
let op = ['+', '-', '*', '/', '^', '!', '(', ')', '']
// s1运算符栈, s2中间结果栈
let s1 = [], s2 = [];
let i = 0;
while (i < len) {
let s = str[i];
if (op.indexOf(s) === -1) {
s2.push(s);
}
else {
// 2.2.1 2.2.2
if (s1.length === 0 || s1[s1.length - 1] === '(' || s === '(') {
s1.push(s);
}
else {
// 2.2.3
if (s === ')') {
let s1_top = s1.pop();
while (s1_top !== '(') {
s2.push(s1_top);
s1_top = s1.pop();;
}
}
// 2.2.4
else {
let cur = op.indexOf(s), top = op.indexOf(s1[s1.length - 1]);
if (priMatrix[cur][top] === '>') {
s1.push(s);
}
else {
s2.push(s1.pop());
i--;
}
}
}
}
i++;
// console.log(i, s1, s2)
}
while (s1.length !== 0) {
s2.push(s1.pop());
}
return s2;
}
// 计算后缀表达式
var evalRPN = function (tokens) {
let op = ["+", "-", "*", "/"];
let stack = [];
let len = tokens.length, i = 0;
while (i < len) {
if (op.indexOf(tokens[i]) === -1) {
stack.push(parseInt(tokens[i]));
} else {
let a1 = stack.pop();
let a2 = stack.pop();
if (tokens[i] === "+") {
a1 = a2 + a1;
stack.push(a1);
}
if (tokens[i] === "-") {
a1 = a2 - a1;
stack.push(a1);
}
if (tokens[i] === "*") {
a1 = a2 * a1;
stack.push(a1);
}
if (tokens[i] === "/") {
a1 = parseInt(a2 / a1);
stack.push(a1);
}
}
i++;
}
return stack.pop();
};
// 计算中缀表达式,先中缀转后缀,再计算后缀表达式
function calculateSuffix(str) {
let suffix = toSuffix(str);
console.log('suffix', suffix)
return evalRPN(suffix.join(''));
}
// let str = '1+2*3-4'; // 1 2 3 * + 4 -
// let str = '1+((2+3)*4)-5' // 1 2 3 + 4 * + 5 -
// let str = '2*(2+3)-8/2' // 2 2 3 + * 8 2 / -
let str = '8/4*2+4/(5-3)*3'
console.log(str)
console.log(calculateInffix(str))
console.log(calculateSuffix(str))
二面(游戏)
侧重前端基础和算法,算法写的很差,挂了
箭头函数,v-model原理,消除字符串中重复的连续字符,二叉树最小高度
f1 = x => x
f2 = x => {x}
f3 = x => ({x})
console.log(f1(1)) // 1
console.log(f2(1)) // undefined
console.log(f3(1)) // {x:1}
消除连续字符串中的重复字符
需要递归,eg: 输入43122213252 ,输出 4252
// eg: 12213242 => 3242
function removeRepeated(str) {
let len = str.length
let res = [], hasRepeated = false
// 记录重复数字出现的起始位置和长度,flag是设置修改start的标识
let start = 0, count = 1, flag = false
let repeated = []
let i = 0
while (i < len - 1) {
if (str[i] === str[i + 1]) {
if (!flag) {
start = i
flag = true
hasRepeated = true
}
count++
}
else {
if (flag) {
repeated.push([start, count])
flag = false
count = 1
}
}
i++
}
// 处理末尾不是字母的情况
if (flag) {
repeated.push([start, count])
}
console.log('repeated', repeated)
// 没有重复元素,直接返回str
if (!hasRepeated) {
return str
}
let nonRepeated = 0, repeatedLen = repeated.length
repeated.forEach((val, index) => {
let [s, c] = [...val]
if (nonRepeated !== s) {
res.push(str.substring(nonRepeated, s))
}
nonRepeated = s + c
if (index === repeatedLen - 1 && nonRepeated !== len) {
res.push(str.substring(nonRepeated, len))
}
})
console.log('res', res)
return removeRepeated(res.join(''))
}
// let s = '12213242' // 3242
// let s = '122132442' // 3
// let s = '122132444' // 32
// let s = '222132444' // 132
let s = '43122213252' // 4252
console.log(removeRepeated(s))
树的最小高度
eg: 输入 [1,2,3,null, null, 4, 5], 输出
function Node(val){
this.val = val
this.left = this.right = null
}
写了几分钟非递归,最好还是用了递归,丢脸
var minDepth = function (head) {
// 类似中序遍历,也可以用BFS或DFS
if (!head) {
return 0
}
let p = head, h = Infinity
let stack = [], l = 0;
while (stack.length || p) {
if (p) {
stack.push([p, ++l])
p = p.left
}
else {
let [n, level] = stack.pop()
if (!n.right && !n.left) {
h = Math.min(h, level)
}
l = level
p = n.right
}
}
return h
}