每日的知识积累,包括 1 个 Ts 类型体操,两个 Leetcode 算法题,三个前端八股文题,四个英语表达积累。
1. 一个类型体操
类型体操题目集合 String to Union
实现一个将接收到的 String 参数转换为一个字母 Union 的类型。
例如:
type Test = "123";
type Result = StringToUnion<Test>; // expected to be "1" | "2" | "3"
分析
这无非就是将元组的遍历变成对字符串的遍历而已;而对于字符串的遍历,我们使用的是 模板字符串。遍历每一个字符,让其与剩余的部分 union 起来。但是这里有个问题就是对于 "" 如何处理?
尝试写出
type StringToUnion<T extends string> = T extends `${infer F}${infer R}`
? F | StringToUnion<R>
: T;
测试用例
type Result = StringToUnion<Test>; // "" | "1" | "2" | "3"
这是不对的哈,没有前面的 "" 才是对的。正确的做法是将 : T 换成 : never T 不满足条件的时候就是 "" 这个时候不需要拼上去。
参考答案
type StringToUnion<T extends string> = T extends `${infer L}${infer R}`
? L | StringToUnion<R>
: never;
经验总结
一定要想清楚递归出口处的值。
2. 两个 Leetcode 题目
刷题的顺序参考这篇文章 LeeCode 刷题顺序
2.1 [48] 旋转图像
给定一个 n × n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。
你必须在 原地 旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。
示例 1:
输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[[7,4,1],[8,5,2],[9,6,3]]
示例 2:
输入:matrix = [[5,1,9,11],[2,4,8,10],[13,3,6,7],[15,14,12,16]]
输出:[[15,13,2,5],[14,3,4,1],[12,6,8,9],[16,7,10,11]]
提示:
n == matrix.length == matrix[i].length
1 <= n <= 20
-1000 <= matrix[i][j] <= 1000
尝试实现:
/**
* @param {number[][]} matrix
* @return {void} Do not return anything, modify matrix in-place instead.
*/
var rotate = function (matrix) {
const r = matrix.length;
if (r === 1) return matrix;
const c = matrix[0].length;
const count = Math.ceil(r / 2);
for (i = 0; i < count; i++) {
for (j = i; j < c - i - 1; j++) {
const tmp = matrix[r - j - 1][i];
matrix[r - j - 1][i] = matrix[r - i - 1][c - j - 1];
matrix[r - i - 1][c - j - 1] = matrix[j][c - i - 1];
matrix[j][c - i - 1] = matrix[i][j];
matrix[i][j] = tmp;
}
}
return matrix;
};
我的思路:
- 如果只有一行则直接输出即可
- 2 行及以上,采用从外到内的旋转方式,首先确定需要转几层,如果是偶数行则遍历 行数/2;如果是奇数行则遍历 行数/2+1 次
- 因此遍历的次数就是 Math.ceiling(行数/2),层数等于行数,记为 i
- 对于每一层,我们从 i 列开始遍历,直到 c - 2*i -1 停止, 记为 j
- 内层遍历的初始元素为(i, j) 我们需要做的就是,交换四个方向上(i,j)的值,所以引入 tmp 保存当前量
- 我们只需计算出(i,j) 对应的其它三个元素的坐标都是哪些即可。首先右侧边的元素为(j , n-i-1) 下侧边元素为(n-i-1,n-j-1) 左侧边的元素为 (n-j-1, i)
- 于是 (i,j) -> (j , n-i-1) -> (n-i-1,n-j-1) -> (n-j-1, i)
- 00 03 33 30 n =4
- 11 12 22 21 n =4
- 01 13 32 20 n =4
得分结果: 32.49% 58.71%
总结提升:
- 还是很容易混淆个数和序列下标的区别。
- 在除 2 之后的取整上想不清楚。
- 向左旋转还是向右旋转的问题。
2.2 [73] 矩阵置零
给定一个 m x n 的矩阵,如果一个元素为 0 ,则将其所在行和列的所有元素都设为 0 。请使用 原地 算法。
示例 1:
输入:matrix = [[1,1,1],[1,0,1],[1,1,1]]
输出:[[1,0,1],[0,0,0],[1,0,1]]
示例 2:
输入:matrix = [[0,1,2,0],[3,4,5,2],[1,3,1,5]]
输出:[[0,0,0,0],[0,4,5,0],[0,3,1,0]]
提示:
m == matrix.length
n == matrix[0].length
1 <= m, n <= 200
-231 <= matrix[i][j] <= 231 - 1
进阶:
一个直观的解决方案是使用 O(mn) 的额外空间,但这并不是一个好的解决方案。
一个简单的改进方案是使用 O(m + n) 的额外空间,但这仍然不是最好的解决方案。
你能想出一个仅使用常量空间的解决方案吗?
尝试完成:
/**
* @param {number[][]} matrix
* @return {void} Do not return anything, modify matrix in-place instead.
*/
var setZeroes = function (matrix) {
const r = matrix.length;
const c = matrix[0].length;
for (let i = 0; i < r; i++) {
for (let j = 0; j < c; j++) {
const isZero = Object.is(matrix[i][j], +0);
if (isZero) {
// 清除同行
for (let _j = 0; _j < c; _j++) {
if (!Object.is(matrix[i][_j], +0)) matrix[i][_j] = -0;
}
// 清除同列
for (let _i = 0; _i < r; _i++) {
if (!Object.is(matrix[_i][j], +0)) matrix[_i][j] = -0;
}
matrix[i][j] = +0;
}
}
}
return matrix;
};
我的思路:
- 利用 js 特有的 Object.is 可以区别正 0 和 负 0,将重置之后的 0 视为 -0,这样就不怕相互覆盖了。
得分结果: 99.56% 99.51%
总结提升:
- -0 +0 是真的好用!
3. 三个前端题目
- 箭头函数的 this 指向
- 与普通的函数不同,js 的箭头函数并没有属于自己的 this,实际上我更愿意将 this 看成是传入函数的一个参数(尽管不是通过参数列表而是通过调用的方式传入的);
- 从这个角度去理解的话,箭头函数实际上在调用的时候并没有被传入这个参数;
- 取而代之的是,在函数执行的过程中,箭头函数使用的是固化/内置的 this 值,这个值就是箭头函数在创建的时候所在作用域的一个对象。
下面使用 babel 转换箭头函数之后的结果说明这个问题:
// 源码
const obj = {
getArrow() {
return () => {
console.log(this);
};
},
};
// 经过babel转译之后的结果
var obj = {
getArrow: function getArrow() {
var _this = this;
return function () {
console.log(_this);
};
},
};
- 手写一个函数,实现数组扁平化的功能 所谓数组扁平化指的就是如果数组中的元素依然是数组,则将内嵌数组中的元素拿出来直接放到上层数组中即可。
2.1 方法一:forEach 和 push
一个最基本的想法就是,创建一个_flat 方法用来遍历这个数组,然后再在历过程中对数组中的每一个元素进行判断;如果元素的类型不是数组则直接 push 到记录数组中去,如果元素的类型是数组,则对此内嵌数组递归调用_flat; 这样相当于对任何一级的数组的每一个元素都进行了遍历,也就是使用深度遍历算法。
function _flat(targetArray, container = []) {
if (!Array.isArray(targetArray)) return container;
targetArray.forEach((item) => {
if (!Array.isArray(item)) {
container.push(item);
} else {
_flat(item, container);
}
});
return container;
}
const rst = _flat([[[[[[1], 2], 3], 4], 5, 6], 7]);
// const rst = _flat('[[[[[[1],2],3],4],5,6],7]');
console.log("rst: ", rst);
2.2 方法二: Array.prototype.flat
ES6 中 Array 的原型上增加了一个名为 flat 的方法,其作用就是将嵌套数组拆包一次;显然没拆一次,整个数组的元素数目是(非严格)单调递增的;根据这个性质,使用 while 循环一直对其拆包,直到某两次拆完之后元素数目相等.
function _flat2(targetArray) {
if (!Array.isArray(targetArray)) return [];
let _loop = targetArray;
while (1) {
const beforeFlat = _loop.length;
const _Arr = _loop.flat();
const afterFlat = _Arr.length;
if (beforeFlat == afterFlat) return _Arr;
_loop = _Arr;
}
}
const rst2 = _flat2([[[[[[1], 2], 3], 4], 5, 6], 7]);
console.log("rst2: ", rst2);
2.3 方法三: findIndex 和 splice
如果在遍历之前就知道为内嵌数组元素的序列号就好了,这样只需要到对应的位置上找到并将其展开就可以了;这个过程一直持续到原数组中再也找不到内嵌数组元素就停止下来。 这种方法是在原来的数组上直接操作的,会改变原数组的内容
function _flat3(targetArray) {
if (!Array.isArray(targetArray)) return [];
while (1) {
const arrItemIndex = targetArray.findIndex((item) => Array.isArray(item));
if (arrItemIndex === -1) return targetArray;
targetArray.splice(arrItemIndex, 1, ...targetArray[arrItemIndex]);
}
}
const rst3 = _flat3([[[[[[1], 2], 3], 4], 5, 6], 7]);
console.log("rst3: ", rst3);
2.4 方法四: stack
- 使用栈这种数据结果,其本质上和递归的算法是完全相同的;但是使用栈来理解的话,会极大的减小心智负担
- 使用两个栈 a 和 b,开始的时候将原始数组整体放入到 a 栈中去,此时 b 栈为空;
- 然后对 a 栈执行下面的动作,直到 a 栈为空:
- 弹栈->判断弹出元素是否是数组,如果不是,则进入栈 b,如果是则拆包一次,再重新进入栈 a
- 最后输出栈 b 即可
function _flat4(targetArray) {
if (!Array.isArray(targetArray)) return [];
// 原始数组全部入a栈
const a = [...targetArray];
const b = [];
while (a.length) {
const _tmp = a.pop();
if (Array.isArray(_tmp)) {
a.push(..._tmp); // 这里不要遍历push,显得很low,a.concat(_tmp)也可
} else {
b.push(_tmp);
}
}
return b;
}
const rst4 = _flat4([[[[[[1], 2], 3], 4], 5, 6], 7]);
console.log("rst4: ", rst4);
- 手写一个函数,实现数组去重的功能 就是字面意思,不难理解!
3.1 利用 set 对象的机制,将数组先变成 set 然后将 set 再变成数组
// 一行搞定
const unique = (array) => [...newSet(array)];
3.2 继续使用两个栈
- 继续使用两个栈 a 和 b,a 执行下面的动作直到空栈
- 弹栈->判断弹出元素在 b 栈中是否存在,如果存在丢弃,如果不存在进入栈 b
const unique2 = (array) => {
if (!Array.isArray(array)) return [];
const a = [...array];
const b = [];
while (a.length) {
const item = a.pop();
// const item = a.shift();
if (b.includes(item)) continue;
b.push(item);
}
return b;
};
可以将pop改成shift有利于保证顺序
3.3 3.2 改进版本
对上面的实现方式进行优化,因为数组通过内容查询元素的效率实在是太低了,所以将 b 从栈改成字典,字典一般是使用 hash 表实现的,在根据查找方面比数组要快
const unique3 = (array) => {
if (!Array.isArray(array)) return [];
const a = [...array];
const b = new Map();
while (a.length) {
const item = a.pop();
// const item = a.shift();
b.set(item, 1);
}
return [...b.keys()];
};
console.log(unique3([1, 1, 1, 1, 2, 3, 4, 345, 345]));
4.四句英语积累
- go over -- to carefully check or review somethng (review)
- This proposal is very important so let's [go over it] one more time.
- Alex, please [go over the report] and [make sure] [there are no mistakes].
- bring up -- to start discussing a topic
- That's a very interesting point - I'm glad you [brought it up].
- I'm sorry, but I don't think [we have time for that]. Maybe you should [bring it up] at the next meeting.