每日知识积累 Day 127

每日的知识积累,包括 五个 Leetcode 算法题,十个前端八股文题,四个英语表达积累。从今天开始倒计时 100 天,到时候换个工作。

1. 五道算法题

第一题: 29. 两数相除 leetcode.cn/problems/di...

plainText 复制代码
给你两个整数,被除数 dividend 和除数 divisor。将两数相除,要求 不使用 乘法、除法和取余运算。

整数除法应该向零截断,也就是截去(truncate)其小数部分。例如,8.345 将被截断为 8 ,-2.7335 将被截断至 -2 。

返回被除数 dividend 除以除数 divisor 得到的 商 。

注意:假设我们的环境只能存储 32 位 有符号整数,其数值范围是 [−231,  231 − 1] 。本题中,如果商 严格大于 231 − 1 ,则返回 231 − 1 ;如果商 严格小于 -231 ,则返回 -231 。



示例 1:

输入: dividend = 10, divisor = 3
输出: 3
解释: 10/3 = 3.33333.. ,向零截断后得到 3 。
示例 2:

输入: dividend = 7, divisor = -3
输出: -2
解释: 7/-3 = -2.33333.. ,向零截断后得到 -2 。


提示:

-231 <= dividend, divisor <= 231 - 1
divisor != 0
js 复制代码
var divide = function (dividend, divisor) {
  if (dividend == 0) {
    return 0;
  }
  if (dividend === -(2 ** 31) && divisor === -1) {
    return 2 ** 31 - 1;
  }
  if (dividend === -(2 ** 31) && divisor === 1) {
    return -(2 ** 31);
  }
  let negative = (dividend ^ divisor) < 0; // 获取最终结果的正负性

  let t = Math.abs(dividend);

  let d = Math.abs(divisor);

  let result = 0;

  for (let i = 31; i >= 0; i--) {
    if (t >>> i >= d) {
      // 这里不能是 >> 不然t将变为负值
      result += 1 << i;
      t -= d << i;
    }
  }

  return negative ? -result : result; // 添加符号不需要那么复杂,只需要在前面添加一个符号即可
};
  1. 实话实说,没做出来,技巧性太强了,上面贴的是标准答案。

得分情况:66.51% 11.17%

总结提升

  1. let negative = (dividend ^ divisor) < 0; 可以快速获取结果的正负性。
  2. >>>>> 的区别,在于前者不会把数变成负数。
  3. return negative ? -result : result; 添加符号不需要那么复杂,只需要在前面添加一个符号即可

我的答案

js 复制代码
/**
 * @param {number} dividend
 * @param {number} divisor
 * @return {number}
 */
var divide = function (dividend, divisor) {
  let prefix = 1;
  if ((dividend < 0 && divisor > 0) || (dividend > 0 && divisor < 0)) {
    prefix = -1;
  }

  dividend = Math.abs(dividend);
  divisor = Math.abs(divisor);

  if (dividend === divisor) {
    return prefix === 1 ? 1 : -1;
  }
  if (divisor === 1) {
    return prefix === 1
      ? Math.min(dividend, 2 ** 31 - 1)
      : Math.max((-2) ** 31, -dividend);
  }

  if (divisor > dividend) return 0;

  const tmp = [divisor, divisor + divisor];
  const counts = [1, 2];

  while (true) {
    const _a = counts[1];
    const _b = tmp[1];
    if (dividend > _b) {
      tmp[0] = _b;
      tmp[1] = _b + _b;
      counts[0] = _a;
      counts[1] = _a + _a;
    } else {
      break;
    }
  }

  while (true) {
    const mid = (tmp[0] >>> 1) + (tmp[1] >>> 1);
    const midCount = (counts[0] >>> 1) + (counts[1] >>> 1);

    if (dividend >= mid) {
      tmp[0] = mid;
      counts[0] = midCount;
    } else {
      tmp[1] = mid;
      counts[1] = midCount;
    }

    // console.log('11', counts, tmp, dividend)
    if (tmp[1] - tmp[0] <= divisor) break;
  }

  let rawResult = counts[0];
  if (Math.abs(dividend) - tmp[0] === divisor) {
    rawResult = counts[1];
  }

  return prefix === 1
    ? Math.min(rawResult, 2 ** 31 - 1)
    : Math.max((-2) ** 31, -rawResult);
};

25.58% 77.68% 有些技法缺少了是做不出来的。上面的代码实现了不使用除法的二分法。

第二题: 507 完美数 leetcode.cn/problems/pe...

plainText 复制代码
对于一个 正整数,如果它和除了它自身以外的所有 正因子 之和相等,我们称它为 「完美数」。

给定一个 整数 n, 如果是完美数,返回 true;否则返回 false。



示例 1:

输入:num = 28
输出:true
解释:28 = 1 + 2 + 4 + 7 + 14
1, 2, 4, 7, 和 14 是 28 的所有正因子。
示例 2:

输入:num = 7
输出:false


提示:

1 <= num <= 108
js 复制代码
/**
 * @param {number} num
 * @return {boolean}
 */
var checkPerfectNumber = function (num) {
  if (num <= 2) return false;
  const upperlimit = ~~Math.sqrt(num);

  let rst = 0;
  for (let i = 1; i <= upperlimit; i++) {
    if (num % i === 0) {
      rst += i + num / i;
    }
  }

  return rst === num + num;
};

得分情况:73.37% 68.47%

第三题: 50 Power(x,n) leetcode.cn/problems/po...

plaintext 复制代码
实现 pow(x, n) ,即计算 x 的整数 n 次幂函数(即,xn )。



示例 1:

输入:x = 2.00000, n = 10
输出:1024.00000
示例 2:

输入:x = 2.10000, n = 3
输出:9.26100
示例 3:

输入:x = 2.00000, n = -2
输出:0.25000
解释:2-2 = 1/22 = 1/4 = 0.25


提示:

-100.0 < x < 100.0
-231 <= n <= 231-1
n 是一个整数
要么 x 不为零,要么 n > 0 。
-104 <= xn <= 104
js 复制代码
/**
 * @param {number} x
 * @param {number} n
 * @return {number}
 */
var myPow = function (x, n) {
  if (n == 0 || n == 1) {
    return n == 0 ? 1 : x;
  } else if (n < 0) {
    return myPow(1 / x, Math.abs(n));
  } else {
    return n % 2 == 0
      ? myPow(x * x, n / 2)
      : myPow(x * x, Math.floor(n / 2)) * x;
  }
};

得分情况:48.12% 13.87% 没做出来,尴尬

总结提升

  1. 不必离散数列因为函数本身就可以递归调用
  2. 对于负数的情况,我们可以抽离提取附加值然后将其变成正数,算出结果之后再将附加值附加作用考虑上即可。

第四题: 372 超级次方 leetcode.cn/problems/su...

plaintext 复制代码
你的任务是计算 ab 对 1337 取模,a 是一个正整数,b 是一个非常大的正整数且会以数组形式给出。



示例 1:

输入:a = 2, b = [3]
输出:8
示例 2:

输入:a = 2, b = [1,0]
输出:1024
示例 3:

输入:a = 1, b = [4,3,3,8,5,2]
输出:1
示例 4:

输入:a = 2147483647, b = [2,0,0]
输出:1198


提示:

1 <= a <= 231 - 1
1 <= b.length <= 2000
0 <= b[i] <= 9
b 不含前导 0
js 复制代码
/**
 * @param {number} a
 * @param {number[]} b
 * @return {number}
 */
var superPow = function (a, b) {
  /**
   * @param {number} a
   * @param {number[]} b
   * @return {number}
   */
  var superPow = function (a, b) {
    a = BigInt(a);
    a = a % 1337n;
    function innerSuperPow(a, b) {
      // console.log('a, b:', a, b)
      // console.log('b:', b)
      if (a === 1n || b === 0n) return 1n;
      if (a === 0n) return 0n;
      if (b > 10n) {
        const _a = b % 10n;
        const _b = (b - _a) / 10n;

        const _c = innerSuperPow(a, _b) % 1337n;
        // console.log(_a, _b, _c)
        return (BigInt(innerSuperPow(_c, 10n)) * innerSuperPow(a, _a)) % 1337n;
      }

      const _rst = a ** b % 1337n;
      // console.log('_rst:', _rst)
      return _rst;
    }

    function innerSuperPow2(x, y, z) {}

    let rst = 1n;

    const len = BigInt(b.length);

    b.forEach((c, i) => {
      rst =
        (rst * innerSuperPow(a, BigInt(c) * 10n ** (len - BigInt(i) - 1n))) %
        1337n;
    });

    return Number(rst);
  };

  console.log(superPow(2147483647, [2, 0, 0]));
};

以上代码超时了!后面再改进吧!

得分情况:--

第五题:682 棒球比赛 leetcode.cn/problems/ba...

plaintext 复制代码
你现在是一场采用特殊赛制棒球比赛的记录员。这场比赛由若干回合组成,过去几回合的得分可能会影响以后几回合的得分。

比赛开始时,记录是空白的。你会得到一个记录操作的字符串列表 ops,其中 ops[i] 是你需要记录的第 i 项操作,ops 遵循下述规则:

整数 x - 表示本回合新获得分数 x
"+" - 表示本回合新获得的得分是前两次得分的总和。题目数据保证记录此操作时前面总是存在两个有效的分数。
"D" - 表示本回合新获得的得分是前一次得分的两倍。题目数据保证记录此操作时前面总是存在一个有效的分数。
"C" - 表示前一次得分无效,将其从记录中移除。题目数据保证记录此操作时前面总是存在一个有效的分数。
请你返回记录中所有得分的总和。



示例 1:

输入:ops = ["5","2","C","D","+"]
输出:30
解释:
"5" - 记录加 5 ,记录现在是 [5]
"2" - 记录加 2 ,记录现在是 [5, 2]
"C" - 使前一次得分的记录无效并将其移除,记录现在是 [5].
"D" - 记录加 2 * 5 = 10 ,记录现在是 [5, 10].
"+" - 记录加 5 + 10 = 15 ,记录现在是 [5, 10, 15].
所有得分的总和 5 + 10 + 15 = 30
示例 2:

输入:ops = ["5","-2","4","C","D","9","+","+"]
输出:27
解释:
"5" - 记录加 5 ,记录现在是 [5]
"-2" - 记录加 -2 ,记录现在是 [5, -2]
"4" - 记录加 4 ,记录现在是 [5, -2, 4]
"C" - 使前一次得分的记录无效并将其移除,记录现在是 [5, -2]
"D" - 记录加 2 * -2 = -4 ,记录现在是 [5, -2, -4]
"9" - 记录加 9 ,记录现在是 [5, -2, -4, 9]
"+" - 记录加 -4 + 9 = 5 ,记录现在是 [5, -2, -4, 9, 5]
"+" - 记录加 9 + 5 = 14 ,记录现在是 [5, -2, -4, 9, 5, 14]
所有得分的总和 5 + -2 + -4 + 9 + 5 + 14 = 27
示例 3:

输入:ops = ["1"]
输出:1


提示:

1 <= ops.length <= 1000
ops[i] 为 "C"、"D"、"+",或者一个表示整数的字符串。整数范围是 [-3 * 104, 3 * 104]
对于 "+" 操作,题目数据保证记录此操作时前面总是存在两个有效的分数
对于 "C" 和 "D" 操作,题目数据保证记录此操作时前面总是存在一个有效的分数
js 复制代码
/**
 * @param {string[]} operations
 * @return {number}
 */
var calPoints = function (operations) {
  const _tmp = [];
  for (let i = 0; i < operations.length; i++) {
    const cur = operations[i];
    switch (cur) {
      case "+":
        _tmp.push(_tmp[_tmp.length - 1] + _tmp[_tmp.length - 2]);
        break;
      case "D":
        _tmp.push(_tmp[_tmp.length - 1] * 2);
        break;
      case "C":
        _tmp.pop();
        break;
      default:
        _tmp.push(parseFloat(cur));
        break;
    }
  }

  // console.log(_tmp);
  return _tmp.reduce((a, i) => a + i, 0);
};

得分情况:60.53% 83.53%

2. 十道面试题

2.1 vue 的生命周期有哪些及每个生命周期做了什么?

beforeCreate 是 new Vue()之后触发的第一个钩子,在当前阶段 data、methods、computed 以及 watch 上的数据和方法都不能被访问。

created 在实例创建完成后发生,当前阶段已经完成了数据观测,也就是可以使用数据,更改数据,在这里更改数据不会触发 updated 函数。可以做一些初始数据的获取,在当前阶段无法与 Dom 进行交互,如果非要想,可以通过 vm.$nextTick 来访问 Dom。

beforeMount 发生在挂载之前,在这之前 template 模板已导入渲染函数编译。而当前阶段虚拟 Dom 已经创建完成,即将开始渲染。在此时也可以对数据进行更改,不会触发 updated。

mounted 在挂载完成后发生,在当前阶段,真实的 Dom 挂载完毕,数据完成双向绑定,可以访问到 Dom 节点,使用$refs 属性对 Dom 进行操作。

beforeUpdate 发生在更新之前,也就是响应式数据发生更新,虚拟 dom 重新渲染之前被触发,你可以在当前阶段进行更改数据,不会造成重渲染。

updated 发生在更新完成之后,当前阶段组件 Dom 已完成更新。要注意的是避免在此期间更改数据,因为这可能会导致无限循环的更新。

beforeDestroy 发生在实例销毁之前,在当前阶段实例完全可以被使用,我们可以在这时进行善后收尾工作,比如清除计时器。

destroyed 发生在实例销毁之后,这个时候只剩下了 dom 空壳。组件已被拆解,数据绑定被卸除,监听被移出,子实例也统统被销毁。

2.2 vue 响应式原理是什么?vue3 的响应式有何不同

  • Vue 在初始化数据时,会使用 Object.defineProperty 重新定义 data 中的所有属性,当页面使用对应属性时,首先会进行依赖收集(收集当前组件的 watcher)如果属性发生变化会通知相关依赖进行更新操作(发布订阅)。

  • Vue3.x 改用 Proxy 替代 Object.defineProperty。因为 Proxy 可以直接监听对象和数组的变化,并且有多达 13 种拦截方法。并且作为新标准将受到浏览器厂商重点持续的性能优化。

❝ Proxy 只会代理对象的第一层,那么 Vue3 又是怎样处理这个问题的呢? ❞

判断当前 Reflect.get 的返回值是否为 Object,如果是则再通过 reactive 方法做代理, 这样就实现了深度观测。

❝ 监测数组的时候可能触发多次 get/set,那么如何防止触发多次呢? ❞

我们可以判断 key 是否为当前被代理对象 target 自身属性,也可以判断旧值与新值是否相等,只有满足以上两个条件之一时,才有可能执行 trigger。

2.3 Vue2.x 和 Vue3.x 的区别

  • 源码组织方式变化:使用 TS 重写
  • 支持 Composition API:基于函数的 API,更加灵活组织组件逻辑(vue2 用的是 options api)
  • 响应式系统提升:Vue3 中响应式数据原理改成 proxy,可监听动态新增删除属性,以及数组变化
  • 编译优化:vue2 通过标记静态根节点优化 diff,Vue3 标记和提升所有静态根节点,diff 的时候只需要对比动态节点内容
  • 打包体积优化:移除了一些不常用的 api(inline-template、filter)
  • 生命周期的变化:使用 setup 代替了之前的 beforeCreate 和 created
  • Vue3 的 template 模板支持多个根标签
  • Vuex 状态管理:创建实例的方式改变,Vue2 为 new Store , Vue3 为 createStore
  • Route 获取页面实例与路由信息:vue2 通过 this 获取 router 实例,vue3 通过使用 getCurrentInstance/ userRoute 和 userRouter 方法获取当前组件实例
  • Props 的使用变化:vue2 通过 this 获取 props 里面的内容,vue3 直接通过 props
  • 父子组件传值:vue3 在向父组件传回数据时,如使用的自定义名称,如 backData,则需要在 emits 中定义一下

2.4 谈一谈对 MVVM 的理解

MVVM 是 Model-View-ViewModel 缩写,也就是把 MVC 中的 Controller 演变成 ViewModel。Model 层代表数据模型,View 代表 UI 组件,ViewModel 是 View 和 Model 层的桥梁,数据会绑定到 viewModel 层并自动将数据渲染到页面中,视图变化的时候会通知 viewModel 层更新数据。

2.5 在 vue2.x 中如何检测数组的变化

使用了函数劫持的方式,重写了数组的方法,Vue 将 data 中的数组进行了原型链重写,指向了自己定义的数组原型方法。这样当调用数组 api 时,可以通知依赖更新。如果数组中包含着引用类型,会对数组中的引用类型再次递归遍历进行监控。这样就实现了监测数组变化。

2.6 V-model 双向绑定的原理是什么

v-model 本质就是一个语法糖,可以看成是 value + input 方法的语法糖。 可以通过 model 属性的 prop 和 event 属性来进行自定义。原生的 v-model,会根据标签的不同生成不同的事件和属性。

2.7 分别说下两个版本的 diff 算法的不同

简单来说,diff 算法有以下过程

  • 同级比较,再比较子节点
  • 先判断一方有子节点一方没有子节点的情况(如果新的 children 没有子节点,将旧的子节点移除)
  • 比较都有子节点的情况(核心 diff)
  • 递归比较子节点

正常 Diff 两个树的时间复杂度是 O(n^3),但实际情况下我们很少会进行跨层级的移动 DOM,所以 Vue 将 Diff 进行了优化,从 O(n^3) -> O(n),只有当新旧 children 都为多个子节点时才需要用核心的 Diff 算法进行同层级比较。

Vue2 的核心 Diff 算法采用了双端比较的算法,同时从新旧 children 的两端开始进行比较,借助 key 值找到可复用的节点,再进行相关操作。相比 React 的 Diff 算法,同样情况下可以减少移动节点次数,减少不必要的性能损耗,更加的优雅。

Vue3.x 借鉴了 ivi 算法和 inferno 算法

在创建 VNode 时就确定其类型,以及在 mount/patch 的过程中采用位运算来判断一个 VNode 的类型,在这个基础之上再配合核心的 Diff 算法,使得性能上较 Vue2.x 有了提升。该算法中还运用了动态规划的思想求解最长递归子序列。

2.8 vue 组件通信方式及原理

  1. 父子组件通信
  • 父->子 props,子->父 <math xmlns="http://www.w3.org/1998/Math/MathML"> o n 、 on、 </math>on、emit
  • 获取父子组件实例 <math xmlns="http://www.w3.org/1998/Math/MathML"> p a r e n t 、 parent、 </math>parent、children
  • Ref 获取实例的方式调用组件的属性或者方法
  • Provide、inject 官方不推荐使用,但是写组件库时很常用
  1. 兄弟组件通信 javascript 代码解读复制代码 Event Bus实现跨组件通信Vue.prototype.$bus = new Vue Vuex

  2. 跨级组件通信

  • bash 代码解读复制代码 Vuex
  • $attrs、$listeners

2.9 说一下 hash 路由和 history 路由

  • location.hash的值实际就是 URL 中#后面的东西。
  • history 实际采用了 HTML5 中提供的 API 来实现,主要有 history.pushState()和 history.replaceState()。

2.11 v-if 和 v-show 的区别

当条件不成立时,v-if 不会渲染 DOM 元素,v-show 操作的是样式(display),切换当前 DOM 的显示和隐藏。

3. 五句英语积累

  1. where is she from?
  2. where is the bathroom?
  3. eleven fifty two -- $11.52
  4. at 3 o'clock
  5. do you have anything cheaper?
  6. do you take credit cards?
相关推荐
耶啵奶膘1 小时前
uniapp-是否删除
linux·前端·uni-app
王哈哈^_^3 小时前
【数据集】【YOLO】【目标检测】交通事故识别数据集 8939 张,YOLO道路事故目标检测实战训练教程!
前端·人工智能·深度学习·yolo·目标检测·计算机视觉·pyqt
cs_dn_Jie3 小时前
钉钉 H5 微应用 手机端调试
前端·javascript·vue.js·vue·钉钉
开心工作室_kaic4 小时前
ssm068海鲜自助餐厅系统+vue(论文+源码)_kaic
前端·javascript·vue.js
有梦想的刺儿4 小时前
webWorker基本用法
前端·javascript·vue.js
cy玩具5 小时前
点击评论详情,跳到评论页面,携带对象参数写法:
前端
qq_390161775 小时前
防抖函数--应用场景及示例
前端·javascript
John.liu_Test6 小时前
js下载excel示例demo
前端·javascript·excel
Yaml46 小时前
智能化健身房管理:Spring Boot与Vue的创新解决方案
前端·spring boot·后端·mysql·vue·健身房管理
PleaSure乐事6 小时前
【React.js】AntDesignPro左侧菜单栏栏目名称不显示的解决方案
前端·javascript·react.js·前端框架·webstorm·antdesignpro