每日的知识积累,包括 1 个 Ts 类型体操,两个 Leetcode 算法题,三个前端八股文题,四个英语表达积累。
1. 一个类型体操
perameters
实现内置的 Parameters 类型,而不是直接使用它,可参考TypeScript官方文档。
分析
对于函数形参的推断,一定要注意的是参数名不重要,甚至可以是相同的。参数的个数完全由类型掌控。
尝试写出
type MyParameters<T extends (...arg: any[]) => any> = T extends (...arg: infer A) => any ? A : never;
测试用例
const foo = (arg1: string, arg2: number): void => {}
type FunctionParamsType = MyParameters<typeof foo> // [arg1: string, arg2: number]
参考答案
type MyParameters<T extends (...args: any[]) => any> = T extends (...p: infer P) => any ? P : never;
经验总结
- 任意函数可以表示为:
(...arg: any[]) => any。 - 剩余形参的推断表示为:
(...arg: infer A)。
2. 四个 Leetcode 题目
刷题的顺序参考这篇文章 LeeCode 刷题顺序
2.1 [383] 赎金信
给你两个字符串:ransomNote 和 magazine ,判断 ransomNote 能不能由 magazine 里面的字符构成。
如果可以,返回 true ;否则返回 false 。
magazine 中的每个字符只能在 ransomNote 中使用一次。
示例 1:
输入:ransomNote = "a", magazine = "b"
输出:false
示例 2:
输入:ransomNote = "aa", magazine = "ab"
输出:false
示例 3:
输入:ransomNote = "aa", magazine = "aab"
输出:true
提示:
1 <= ransomNote.length, magazine.length <= 105
ransomNote 和 magazine 由小写英文字母组成
尝试实现:
/**
* @param {string} ransomNote
* @param {string} magazine
* @return {boolean}
*/
var canConstruct = function(ransomNote, magazine) {
let rec = {};
for(let i = 0; i < magazine.length; i++){
if(typeof rec[magazine[i]] === 'undefined') {
rec[magazine[i]] = 1;
} else {
rec[magazine[i]] += 1;
}
}
for(let i = 0; i < ransomNote.length; i++){
const cur = ransomNote[i];
if(typeof rec[cur] === 'undefined') return false;
if(rec[cur] <=0) return false;
rec[cur] -= 1;
}
return true;
};
我的思路: 统计 magazine 中每个字符的个数,然后遍历 ransomeNote, 对每个字符查询,如果没有就返回 false, 有的话统计结果中个数 -1.
得分结果: 52.52% 77.66%
总结提升: 一开始想在第二个中逐个查询,这是不对的,效率太低了。
2.2 [242] 有效的字母异位词
给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的 字母异位词。
示例 1:
输入: s = "anagram", t = "nagaram"
输出: true
示例 2:
输入: s = "rat", t = "car"
输出: false
提示:
1 <= s.length, t.length <= 5 * 104
s 和 t 仅包含小写字母
进阶: 如果输入字符串包含 unicode 字符怎么办?你能否调整你的解法来应对这种情况?
尝试完成:
/**
* @param {string} s
* @param {string} t
* @return {boolean}
*/
var isAnagram = function(s, t) {
const sR ={};
const tR ={};
if(s.length !== t.length) return false;
for(let i = 0; i < s.length; i++) {
if(typeof sR[s[i]] === 'undefined'){
sR[s[i]] = 1;
} else {
sR[s[i]] += 1;
}
if(typeof tR[t[i]] === 'undefined'){
tR[t[i]] = 1;
} else {
tR[t[i]] += 1;
}
}
for(let j in sR) {
if(sR[j] !== tR[j]) return false;
}
return true;
};
我的思路:
- 先使用排序,然后使用统计的方法,看看谁的效率高一些。
得分结果: 87.14% 84.95%
总结提升:
- 排序输太多了,差别为:87.14% 84.95% 比 26.94% 9.07%
2.3 [49] 字母异位词分组
给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。
字母异位词 是由重新排列源单词的所有字母得到的一个新单词。
示例 1:
输入: strs = ["eat", "tea", "tan", "ate", "nat", "bat"]
输出: [["bat"],["nat","tan"],["ate","eat","tea"]]
示例 2:
输入: strs = [""]
输出: [[""]]
示例 3:
输入: strs = ["a"]
输出: [["a"]]
提示:
1 <= strs.length <= 104
0 <= strs[i].length <= 100
strs[i] 仅包含小写字母
尝试完成:
/**
* @param {string[]} strs
* @return {string[][]}
*/
var groupAnagrams = function(strs) {
const n = strs.length;
if(n===1) return [strs];
const rec={};
for(let i = 0; i < n; i++) {
const cur = strs[i].split("").sort().join("");
if(typeof rec[cur] === "undefined") {
rec[cur] = [strs[i]];
} else {
rec[cur].push(strs[i]);
}
}
const rst = [];
for(let j in rec) {
rst.push(rec[j]);
}
return rst;
};
我的思路:
- 这道题的本质考察的是如何计算字符串与字符排列顺序无关的特征值,我们可以将字符串排序之后的结果当成此特征值。
得分结果: 80.68% 85.19%
2.4 [451] 根据字符出现频率排序
给定一个字符串 s ,根据字符出现的 频率 对其进行 降序排序 。一个字符出现的 频率 是它出现在字符串中的次数。
返回 已排序的字符串 。如果有多个答案,返回其中任何一个。
示例 1:
输入: s = "tree"
输出: "eert"
解释: 'e'出现两次,'r'和't'都只出现一次。
因此'e'必须出现在'r'和't'之前。此外,"eetr"也是一个有效的答案。
示例 2:
输入: s = "cccaaa"
输出: "cccaaa"
解释: 'c'和'a'都出现三次。此外,"aaaccc"也是有效的答案。
注意"cacaca"是不正确的,因为相同的字母必须放在一起。
示例 3:
输入: s = "Aabb"
输出: "bbAa"
解释: 此外,"bbaA"也是一个有效的答案,但"Aabb"是不正确的。
注意'A'和'a'被认为是两种不同的字符。
提示:
1 <= s.length <= 5 * 105
s 由大小写英文字母和数字组成
尝试完成:
/**
* @param {string} s
* @return {string}
*/
var frequencySort = function(s) {
const rec = {};
const n = s.length;
if(n===1) return s;
for(let i = 0; i < n; i++) {
const cur = s[i];
if(typeof rec[cur] === 'undefined'){
rec[cur] = 1;
}else{
rec[cur] += 1;
}
}
let arr =[];
for(let j in rec) {
if(typeof arr[rec[j]] === 'undefined') {
arr[rec[j]] = [j];
} else {
arr[rec[j]].push(j);
}
}
arr = arr.map((v, i) => {
return v.map(u=>u.repeat(i)).join("");
})
return arr.reverse().join("");
};
我的思路:
- 先统计每个字符出现的次数,然后遍历统计结果得到一个数组,最后将数组中的元素连接起来即可。
得分结果: 16.67% 70.14%
总结提升:
- 用时太久了,遍历的次数有些多。
3. 三个前端题目
- 实现Array.prototype.filter
首先必须要明白filter函数的执行原理:
创建一个空数组(称为结果数组)来存储满足条件的元素。
遍历原始数组中的每个元素。
对于每个元素,调用传递给 filter() 方法的回调函数,并将当前元素作为参数传递给回调函数。
回调函数根据设定的条件对当前元素进行评估。如果回调函数返回 true,则表示当前元素满足条件,将其添加到结果数组中。
继续遍历原始数组中的下一个元素,并重复步骤 3 和 4,直到遍历完所有元素。
返回结果数组,其中包含满足条件的元素。
function myFilter (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();
if (cb(_v, _stack.length-1, _stack) === true) {
_result.push(_v);
}
}
return _result;
}
- 数组上有哪些方法,分类说明
- 数组转string: toString toLocalString join
- 元素操作: pop push shift unshift splice
- 排序和倒序: sort reverse
- 切片和填充: slice fill
- 归并和合并: reduce reduceRight concat
- 查找: find findIndex indexOf lastIndexOf includes
- 迭代: every some filter forEach map
- js中的位运算和原码、反码、补码
- 六种位运算:& | ~ ^ >> <<
- 正数三码合一
- 负数:反码是原码的符号位不动,其余位取反;补码是反码+1
- +0和-0的补码是相同的
- 使用位运算可以在一些特殊场景下使代码变得简单高效,包括:
快速取整:x|0 或者 ~~x
判断奇偶: x&1
找到左侧偶数: x&(x-1) 下面第五种情况除外
除2并取整: x>>2 或者取平均数:(a+b)>>2
乘2并取整: x<<2
判断是否是2的幂次方: x&(x-1)是不是为0
交换x和y的值: 连续执行三次异或 a = a ^ b; b = a ^ b; a = a ^ b;
检查二进制的n位是否为1:x&(1<<n)的结果是否为0
判断两个数是否同正负:(a^b) > 0
权限控制,使用二进制的每一bit表示对某个权限的控制权,当二进制变成十进制之后,可以使用很小的数字表示很多权限
4.四句英语积累
- cautiously optimistic
- I'm [feeling cautiously optimistic that] I'll [get a good result].
- Heath officials say they are cautiouly optimistic about the ...
- optimism -- noun of optimistic
- We'[re full of optimism for] the future.
- Her [optimism is what makes] her a great leader.
- I hope so / I hope not.
- I certainly hope so / I certainly hope not.