每日的知识积累,包括 1 个 Ts 类型体操,两个 Leetcode 算法题,三个前端八股文题,四个英语表达积累。
1. 一个类型体操
Rermutation
实现联合类型的全排列,将联合类型转换成所有可能的全排列数组的联合类型。
分析
对于联合类型的操作,遵循下面的原则:逐个选出每一种类型并进行冻结,然后计算出每一种结果之后再将所有的结果联合起来。
尝试写出
type Permutation<T, K = T> = T extends never
? []
: [K, ...Permutation<Exclude<T, K>>];
测试用例
type perm = Permutation<"A" | "B" | "C">; // never
参考答案
type Permutation2<T, K = T> = [T] extends [never]
? []
: K extends K
? [K, ...Permutation<Exclude<T, K>>]
: never;
经验总结
尝试写出来的答案是不对的,和标准答案的差距在于:
T extends never 和 [T] extends [never]有什么区别。K extends K的作用是什么。
2. 四个 Leetcode 题目
刷题的顺序参考这篇文章 LeeCode 刷题顺序
2.1 [541] 反转字符串
给定一个字符串 s 和一个整数 k,从字符串开头算起,每计数至 2k 个字符,就反转这 2k 字符中的前 k 个字符。
如果剩余字符少于 k 个,则将剩余字符全部反转。
如果剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符,其余字符保持原样。
示例 1:
输入:s = "abcdefg", k = 2
输出:"bacdfeg"
示例 2:
输入:s = "abcd", k = 2
输出:"bacd"
提示:
1 <= s.length <= 104
s 仅由小写英文组成
1 <= k <= 104
尝试实现:
/**
* @param {string} s
* @param {number} k
* @return {string}
*/
var reverseStr = function (s, k) {
const n = s.length;
if (n <= k) return s.split("").reverse().join("");
if (n <= 2 * k)
return s.slice(0, k).split("").reverse().join("").concat(s.slice(k));
const p = n % (2 * k);
const q = (n - p) / (2 * k);
let rst = "";
let i = 0;
for (i = 0; i < q; i++) {
const cur = s
.slice(i * 2 * k, i * 2 * k + k)
.split("")
.reverse()
.join("")
.concat(s.slice(i * 2 * k + k, i * 2 * k + 2 * k));
rst = rst.concat(cur);
}
rst = rst.concat(
s
.slice(i * 2 * k, i * 2 * k + k)
.split("")
.reverse()
.join("")
);
rst = rst.concat(s.slice(i * 2 * k + k, i * 2 * k + 2 * k));
return rst;
};
我的思路:
- 先处理长度小于 k 2k 的两种情况。
- 对于长度大于 2k 的先处理前面 2k 个然后调用本函数处理剩下的尾部。
得分结果: 43.23% 80.52%
总结提升:
- 如果想要再循环完成之后再次利用循环变量,则将其定义在循环外部。
- for 循环结束之后,循环变量的值相较于最后一次在循环体内还会变化一次。
2.2 [557] 反转字符串中的单词
给定一个字符串 s ,你需要反转字符串中每个单词的字符顺序,同时仍保留空格和单词的初始顺序。
示例 1:
输入:s = "Let's take LeetCode contest"
输出:"s'teL ekat edoCteeL tsetnoc"
示例 2:
输入: s = "Mr Ding"
输出:"rM gniD"
提示:
1 <= s.length <= 5 * 104
s 包含可打印的 ASCII 字符。
s 不包含任何开头或结尾空格。
s 里 至少 有一个词。
s 中的所有单词都用一个空格隔开。
尝试完成:
/**
* @param {string} s
* @return {string}
*/
var reverseWords = function (s) {
const arr = s.split(" ");
const revr = arr.map((v) => v.split("").reverse().join(""));
return revr.join(" ");
};
我的思路:
- 感觉没啥好说的,注意 js 语言本身的限制。
得分结果: 78.65% 74.16%
总结提升:
- 收集一些之前没有注意到的语法限制:
console.log("1 2 3".split(" ")); // [ '1', '2', '3' ]
console.log("1 2 3".split(" ")); // [ '1', ' ', '2', '3' ]
console.log("1 2 3".split(" ")); // [ '1', ' ', ' ', '2', '3' ]
console.log(" 2 3".split(" ")); // [ ' ', ' ', ' ', '2', '3' ]
console.log("2 3 ".split(" ")); // [ '2', '3', '', '', '' ]
- 使用空格分隔,如果原来的字符串内右空格,则原来连续的空格只会损失一个。但如果空格是首的话则一个也不会损失; 如果是尾部则全部变成空字符串。
2.3 [151] 反转字符串中的单词
给你一个字符串 s ,请你反转字符串中 单词 的顺序。
单词 是由非空格字符组成的字符串。s 中使用至少一个空格将字符串中的 单词 分隔开。
返回 单词 顺序颠倒且 单词 之间用单个空格连接的结果字符串。
注意:输入字符串 s中可能会存在前导空格、尾随空格或者单词间的多个空格。返回的结果字符串中,单词间应当仅用单个空格分隔,且不包含任何额外的空格。
示例 1:
输入:s = "the sky is blue"
输出:"blue is sky the"
示例 2:
输入:s = " hello world "
输出:"world hello"
解释:反转后的字符串中不能存在前导空格和尾随空格。
示例 3:
输入:s = "a good example"
输出:"example good a"
解释:如果两个单词间有多余的空格,反转后的字符串需要将单词间的空格减少到仅有一个。
提示:
1 <= s.length <= 104
s 包含英文大小写字母、数字和空格 ' '
s 中 至少存在一个 单词
尝试完成:
/**
* @param {string} s
* @return {string}
*/
var reverseWords = function (s) {
const arr = s.split(" ");
const _arr = arr.filter((v) => v !== " " && v !== "");
return _arr.reverse().join(" ");
};
我的思路:
- 感觉没啥好说的,注意 js 语言本身的限制。
得分结果: 66.18% 69.28%
2.4 [387] 字符串中的第一个唯一字符
给定一个字符串 s ,找到 它的第一个不重复的字符,并返回它的索引 。如果不存在,则返回 -1 。
示例 1:
输入: s = "leetcode"
输出: 0
示例 2:
输入: s = "loveleetcode"
输出: 2
示例 3:
输入: s = "aabb"
输出: -1
提示:
1 <= s.length <= 105
s 只包含小写字母
尝试完成:
/**
* @param {string} s
* @return {number}
*/
var firstUniqChar = function (s) {
const n = s.length;
if (n === 0) return -1;
if (n === 1) return 0;
const rec = {};
for (let i = 0; i < n; i++) {
const current = s[i];
if (typeof rec[current] === "undefined") {
rec[current] = i;
} else {
rec[current] = null;
}
}
for (let j in rec) {
if (rec[j] !== null) return rec[j];
}
return -1;
};
我的思路:
- 第一遍统计,第二遍筛选。
得分结果: 15.70% 82.75%
总结提升:
- 题目中说了返回第一个唯一字符,唯一字符就是 count 为 1 的字符,但是确定是不是第一个则需要将所有 count = 1 的下标进行比较,这显然时间上不友好;实际上 js 的 object 在遍历的时候遵循先来后到的原则,由于我们是从左到右遍历的,所以收集到的第一个就是目标值。
- 使用
for(let i in {})对对象的键进行遍历,遍历遵循属性的定义顺序,且只遍历 ownProperty.
3. 三个前端题目
- 实现 Array.prototype.map 首先必须要明白 map 函数的执行原理:
遍历原始数组:对于调用 map() 方法的数组,会遍历每个元素。
对每个元素应用回调函数:对于每个元素,都会调用传递给 map() 方法的回调函数,并传入三个参数:当前元素的值、当前元素的索引和原始数组本身。回调函数用来对每个元素进行处理。
构建新数组:将回调函数返回的结果存储在新的数组中。这些结果按照原始数组的顺序排列,构成了新数组。
返回新数组:当遍历完所有元素并处理完成后,map() 方法返回包含处理结果的新数组。
function myMap(cb) {
if (!Array.isArray(this)) throw new Error("must be called by array");
const _stack = [...this];
const _result = [];
while (_stack.length) {
const _v = _stack.pop();
const _tmp = cb(_v, _stack.length - 1, this);
// do some your judgement
_result.push(_tmp);
}
return _result;
}
- 实现Array.prototype.reduce
首先必须要明白reduce函数的执行原理:
初始化累加器:首先,将累加器(accumulator)的初始值设置为指定的初始值(或者默认为数组的第一个元素,如果没有提供初始值)。
遍历数组元素:从数组的第一个元素开始,逐个遍历每个元素。
应用回调函数:对于每个元素,都会调用传递给 reduce() 方法的回调函数,并传入四个参数:累加器、当前元素的值、当前元素的索引和原始数组本身。回调函数用来根据当前元素的值和累加器的值进行计算,并返回新的累加器值。
更新累加器:将回调函数返回的新累加器值,作为下一次迭代的累加器值。
返回最终结果:当遍历完所有元素后,reduce() 方法返回最终的累加器值作为计算结果。
function myReduce (cb, initialValue) {
if(!Array.isArray(this)) throw new Error('must be called by array');
let _acc = initialValue ?? this[0];
const startIndex = Number(initialValue === undefined);
for (let i = startIndex; i < this.length; i++) {
_acc = cb(_acc, this[i], i , this); // 与循环变量相关的量的更新、与循环变量无关的量的更新以及不变值
}
return _acc;
}
- 实现Array.prototype.reduceRight
首先必须要明白reduceRight函数的执行原理:
初始化累加器:首先,将累加器(accumulator)的初始值设置为指定的初始值(或者默认为数组的最后一个元素,如果没有提供初始值)。
逆向遍历数组元素:从数组的最后一个元素开始,逐个逆向遍历每个元素。
应用回调函数:对于每个元素,都会调用传递给 reduceRight() 方法的回调函数,并传入四个参数:累加器、当前元素的值、当前元素的索引和原始数组本身。回调函数用来根据当前元素的值和累加器的值进行计算,并返回新的累加器值。
更新累加器:将回调函数返回的新累加器值,作为下一次迭代的累加器值。
返回最终结果:当逆向遍历完所有元素后,reduceRight() 方法返回最终的累加器值作为计算结果。
function myReduce (cb, initialValue) {
if(!Array.isArray(this)) throw new Error('must be called by array');
let _acc = initialValue ?? this.at(-1);
const startIndex = initialValue ? this.length -1 : this.length -2;
for (let i = startIndex; i > 0; i--) {
_acc = cb(_acc, this[i], i , this);
}
return _acc;
}
4.四句英语积累
- let's hope + subject
- Let's hope life can get back to normal next year.
- Let's hope the weather improves.
- Let's hope so -- 希望如此
- optimistic -- hope or believe that good things will happen in the future [adj]
- I['m optimistic about] 2021
- The government [is optimistic that] the economy will do better than expected next year.