每日知识积累 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?
相关推荐
加班是不可能的,除非双倍日工资3 小时前
css预编译器实现星空背景图
前端·css·vue3
wyiyiyi4 小时前
【Web后端】Django、flask及其场景——以构建系统原型为例
前端·数据库·后端·python·django·flask
gnip4 小时前
vite和webpack打包结构控制
前端·javascript
excel5 小时前
在二维 Canvas 中模拟三角形绕 X、Y 轴旋转
前端
阿华的代码王国5 小时前
【Android】RecyclerView复用CheckBox的异常状态
android·xml·java·前端·后端
一条上岸小咸鱼5 小时前
Kotlin 基本数据类型(三):Booleans、Characters
android·前端·kotlin
Jimmy5 小时前
AI 代理是什么,其有助于我们实现更智能编程
前端·后端·ai编程
ZXT5 小时前
promise & async await总结
前端
Jerry说前后端5 小时前
RecyclerView 性能优化:从原理到实践的深度优化方案
android·前端·性能优化
画个太阳作晴天5 小时前
A12预装app
linux·服务器·前端