每日的知识积累,包括 五个 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; // 添加符号不需要那么复杂,只需要在前面添加一个符号即可
};
- 实话实说,没做出来,技巧性太强了,上面贴的是标准答案。
得分情况:66.51% 11.17%
总结提升
let negative = (dividend ^ divisor) < 0;
可以快速获取结果的正负性。>>>
和>>
的区别,在于前者不会把数变成负数。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% 没做出来,尴尬
总结提升
- 不必离散数列因为函数本身就可以递归调用
- 对于负数的情况,我们可以抽离提取附加值然后将其变成正数,算出结果之后再将附加值附加作用考虑上即可。
第四题: 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 组件通信方式及原理
- 父子组件通信
- 父->子 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 官方不推荐使用,但是写组件库时很常用
-
兄弟组件通信 javascript 代码解读复制代码 Event Bus
实现跨组件通信
Vue.prototype.$bus = new Vue Vuex -
跨级组件通信
- 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. 五句英语积累
- where is she from?
- where is the bathroom?
- eleven fifty two -- $11.52
- at 3 o'clock
- do you have anything cheaper?
- do you
take
credit cards?