字节跳动2021春招前端开发面经

一面(独立闭环业务)

侧重计算机基础,问了很多OS和网络,本以为没过的收到了二面。
PS. 一面是独立闭环业务,二面变成了游戏,难道是一面挂了二面被捞么???

OS

进程线程的区别、并行并发区别、进程通信和同步(只记得信号量和管道)

网络

简述七层模型、传输层协议、TCP为什么要三次握手(这个答了洪泛攻击,实际上问的是由于网络拥塞或延迟,第三次握手的包重传且正常关闭连接后,收到了迟到的第三次握手的ACK包)、接着问洪泛攻击预防限制指定时间内连接请求次数、SYN缓存(收到SYN时仅仅分配有限空间)、SYN cookie(收到SYN时不分配任何资源,根据四元组精心构造SYN+ACK的序号,这个序号就是SYN cookie)

TCP SYN洪泛攻击的原理及防御方法

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
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值