- 作者:老汪软件技巧
- 发表时间:2024-09-09 07:01
- 浏览量:
每日的知识积累,包括 1 个 Ts 类型体操,两个 Leetcode 算法题,三个前端八股文题,四个英语表达积累。
1. 一个类型体操
类型体操题目集合
Append Argument
实现一个泛型 AppendArgument,对于给定的函数类型 Fn,以及一个任意类型 A,返回一个新的函数 G。G 拥有 Fn 的所有参数并在末尾追加类型为 A 的参数。
分析
对于函数形参的推断,一定要注意的是参数名不重要,甚至可以是相同的。参数的个数完全由类型掌控。argument 本身不是 tuple 但是可以用 tuple 表示之。
尝试写出
type AppendArgumentextends (...arg: any[]) => any, K> = T extends (...arg: infer A) => any ? [...A, _:K] : never;
测试用例
type Fn = (a: number, b: string) => number
type Result = AppendArgument<Fn, boolean> // (a: number, b: string, x: boolean) => number
参考答案
type AppendArgument<Fn extends (...params: any[]) => any, A> = Fn extends (...p: infer P) => infer R
? (...params: [...p: P, a: A]) => R
: never;
经验总结注意这里的 [...A, _:K] 这表示形参的名称一点都不重要,即使和测试用例中的重复了也没有任何关系。2. 四个 Leetcode 题目
刷题的顺序参考这篇文章 LeeCode 刷题顺序
2.1 [423] 从英文中重建数字
给你一个字符串 s ,其中包含字母顺序打乱的用英文单词表示的若干数字(0-9)。按 升序 返回原始的数字。
示例 1:
输入:s = "owoztneoer"
输出:"012"
示例 2:
输入:s = "fviefuro"
输出:"45"
提示:
1 <= s.length <= 105
s[i] 为 ["e","g","f","i","h","o","n","s","r","u","t","w","v","x","z"] 这些字符之一
s 保证是一个符合题目要求的字符串
尝试实现:
/**
* @param {string} s
* @return {string}
*/
var originalDigits = function(s) {
const rst = {};
for(let i = 0; i < s.length; i++) {
const cur = s[i];
if(typeof rst[cur] === 'undefined'){
rst[cur] = 1;
}else{
rst[cur] += 1;
}
}
const arr =[];
const num2 = rst['w'];
if(num2) {
if(rst['t']) rst['t'] -= num2;
if(rst['w']) rst['w'] -= num2;
if(rst['o']) rst['o'] -= num2;
arr[2] = num2;
}
const num4 = rst['u'];
if(num4) {
if(rst['f']) rst['f'] -= num4;
if(rst['o']) rst['o'] -= num4;
if(rst['u']) rst['u'] -= num4;
if(rst['r']) rst['r'] -= num4;
arr[4] = num4;
}
const num6 = rst['x'];
if(num6) {
if(rst['s']) rst['s'] -= num6;
if(rst['i']) rst['i'] -= num6;
if(rst['x']) rst['x'] -= num6;
arr[6] = num6;
}
const num8 = rst['g'];
if(num8) {
if(rst['e']) rst['e'] -= num8;
if(rst['i']) rst['i'] -= num8;
if(rst['g']) rst['g'] -= num8;
if(rst['h']) rst['h'] -= num8;
if(rst['t']) rst['t'] -= num8;
arr[8] = num8;
}
const num0 = rst['z'];
if(num0) {
if(rst['z']) rst['z'] -= num0;
if(rst['e']) rst['e'] -= num0;
if(rst['r']) rst['r'] -= num0;
if(rst['o']) rst['o'] -= num0;
arr[0] = num0;
}
const num1 = rst['o'];
if(num1) {
if(rst['o']) rst['o'] -= num1;
if(rst['n']) rst['n'] -= num1;
if(rst['e']) rst['e'] -= num1;
arr[1] = num1;
}
const num3 = rst['t'];
if(num3) {
if(rst['t']) rst['t'] -= num3;
if(rst['h']) rst['h'] -= num3;
if(rst['r']) rst['r'] -= num3;
if(rst['e']) rst['e'] -= num3;
if(rst['e']) rst['e'] -= num3;
arr[3] = num3;
}
const num5 = rst['f'];
if(num5) {
if(rst['f']) rst['f'] -= num5;
if(rst['i']) rst['i'] -= num5;
if(rst['v']) rst['v'] -= num5;
if(rst['e']) rst['e'] -= num5;
arr[5] = num5;
}
const num7 = rst['s'];
if(num7) {
if(rst['s']) rst['s'] -= num7;
if(rst['e']) rst['e'] -= num7;
if(rst['v']) rst['v'] -= num7;
if(rst['e']) rst['e'] -= num7;
if(rst['n']) rst['n'] -= num7;
arr[7] = num7;
}
const num9 = rst['i'];
if(num9) {
if(rst['n']) rst['n'] -= num9;
if(rst['i']) rst['i'] -= num9;
if(rst['n']) rst['n'] -= num9;
if(rst['e']) rst['e'] -= num9;
arr[9] = num9;
}
let result = "";
arr.forEach((v,i)=>{
result += (i+"").repeat(v)
})
return result;
};
我的思路:
// 从 0-9 有的单词中的字符是独一无二的,先排除这些单词,然后再排除独特性差一点的
//
// t[w]o fo[u]r si[x] ri[g]ht [z]ero
// [o]ne [t]hree [f]ive [s]even
// n[i]ne
得分结果: 26.67% 86.67%
总结提升:forEach 也会直接跳过 empty 所以不用担心其导致的报错。
2.2 [657] 机器人是否能返回原点
在二维平面上,有一个机器人从原点 (0, 0) 开始。给出它的移动顺序,判断这个机器人在完成移动后是否在 (0, 0) 处结束。
移动顺序由字符串 moves 表示。字符 move[i] 表示其第 i 次移动。机器人的有效动作有 R(右),L(左),U(上)和 D(下)。
如果机器人在完成所有动作后返回原点,则返回 true。否则,返回 false。
注意:机器人“面朝”的方向无关紧要。 “R” 将始终使机器人向右移动一次,“L” 将始终向左移动等。此外,假设每次移动机器人的移动幅度相同。
示例 1:
输入: moves = "UD"
输出: true
解释:机器人向上移动一次,然后向下移动一次。所有动作都具有相同的幅度,因此它最终回到它开始的原点。因此,我们返回 true。
示例 2:
输入: moves = "LL"
输出: false
解释:机器人向左移动两次。它最终位于原点的左侧,距原点有两次 “移动” 的距离。我们返回 false,因为它在移动结束时没有返回原点。
提示:
1 <= moves.length <= 2 * 104
moves 只包含字符 'U', 'D', 'L' 和 'R'
尝试完成:
/**
* @param {string} moves
* @return {boolean}
*/
var judgeCircle = function(moves) {
const n = moves.length;
if(n % 2 !== 0) return false;
const red = {};
for(let i = 0; i < n; i++) {
const cur = moves[i];
if(typeof red[cur] === 'undefined'){
red[cur] = 1;
}else {
red[cur] += 1;
}
}
return red[U]===red[D] && red[R]===red[L];
};
我的思路:
统计,然后判断相反方向运动次数是否一致。
得分结果: 22.34% 89.36%
总结提升:
这样做太慢了,我们完全可以一次遍历出结果的。
/**
* @param {string} moves
* @return {boolean}
*/
var judgeCircle = function(moves) {
const n = moves.length;
if(n % 2 !== 0) return false;
let rst = 0;
for(let i = 0; i < n; i++) {
const cur = moves[i];
if(cur === "U") rst+=1;
if(cur === "D") rst-=1;
if(cur === "L") rst+=0.5;
if(cur === "R") rst-=0.5;
}
return rst === 0;
};
2.3 [551] 学生出勤记录
给你一个字符串 s 表示一个学生的出勤记录,其中的每个字符用来标记当天的出勤情况(缺勤、迟到、到场)。记录中只含下面三种字符:
'A':Absent,缺勤
'L':Late,迟到
'P':Present,到场
如果学生能够 同时 满足下面两个条件,则可以获得出勤奖励:
按 总出勤 计,学生缺勤('A')严格 少于两天。
学生 不会 存在 连续 3 天或 连续 3 天以上的迟到('L')记录。
如果学生可以获得出勤奖励,返回 true ;否则,返回 false 。
示例 1:
输入:s = "PPALLP"
输出:true
解释:学生缺勤次数少于 2 次,且不存在 3 天或以上的连续迟到记录。
示例 2:
输入:s = "PPALLL"
输出:false
解释:学生最后三天连续迟到,所以不满足出勤奖励的条件。
提示:
1 <= s.length <= 1000
s[i] 为 'A'、'L' 或 'P'
尝试完成:
/**
* @param {string} s
* @return {boolean}
*/
var checkRecord = function(s) {
const n = s.length;
let numA = 0;
let numL = 0;
for(let i = 0; i < n; i++) {
const cur = s[i];
if(cur === 'A') numA++;
if(cur === 'L') numL++;
if(cur !== 'L') numL=0;
if(numA>=2 || numL>=3 ) return false;
}
return true;
};
我的思路:
连续迟到需要注意如何处理。
得分结果: 11.99% 82.98%
2.4 [696] 计数二进制子串
给定一个字符串 s,统计并返回具有相同数量 0 和 1 的非空(连续)子字符串的数量,并且这些子字符串中的所有 0 和所有 1 都是成组连续的。
重复出现(不同位置)的子串也要统计它们出现的次数。
示例 1:
输入:s = "00110011"
输出:6
解释:6 个子串满足具有相同数量的连续 1 和 0 :"0011"、"01"、"1100"、"10"、"0011" 和 "01" 。
注意,一些重复出现的子串(不同位置)要统计它们出现的次数。
另外,"00110011" 不是有效的子串,因为所有的 0(还有 1 )没有组合在一起。
示例 2:
输入:s = "10101"
输出:4
解释:有 4 个子串:"10"、"01"、"10"、"01" ,具有相同数量的连续 1 和 0 。
提示:
1 <= s.length <= 105
s[i] 为 '0' 或 '1'
尝试完成:
/**
* @param {string} s
* @return {number}
*/
var countBinarySubstrings = function(s) {
const n = s.length;
if(n<2) return 0;
function calc(_i) {
let rst = 1;
let j = 1;
while(true) {
if(s[_i]===s[_i+j] && s[_i-j]===s[_i-1-j]){
rst+=1;
j++;
}else{
return rst;
}
}
}
let rst = 0;
for(let i = 1; i < n; i++) {
if(s[i] !== s[i-1]) rst += calc(i);
}
return rst;
};
我的思路:
观察到每一次这种统计都会发生在1变成0或者0变成1的时候,因此,我们构建一个新的函数独立完成这件事情;使用一点点面向对象的思想就可以使整个逻辑非常的清晰。
得分结果: 33.33% 57.89%
3. 三个前端题目实现Array.prototype.push
原理:push() 方法用于将一个或多个元素添加到数组的末尾,并返回新数组的长度。
实现:可以巧妙地使用length属性完成这一功能
返回值:此方法的返回值为变化之后的数组的长度
function myPush (...eles) {
if(!Array.isArray(this)) throw new Error('must be called by array');
const _tmp = [...eles];
for (let i = 0; i < _tmp.length; i++) {
this[this.length] = _tmp[i]
}
return this.length;
}
实现Array.prototype.pop
原理:pop() 方法从数组中删除并返回最后一个元素。
实现:可以巧妙地使用length属性完成这一功能,并且改变length的值之后,对应的索引属性自动消失
返回值:被删除的元素或者undefined
function myPop () {
if(!Array.isArray(this)) throw new Error('must be called by array');
if(this.length===0) return undefined;
const _tmp = this[this.length - 1];
this.length--;
return _tmp;
}
实现Array.prototype.shift
原理:shift() 方法从数组中删除并返回第一个元素,同时将其他元素往前移动。
实现:由于删除的是数组的第一个元素,所以数组的其它元素都要发生位移,因此就需要遍历的移动每一个数组中的元素,这一点比起pop非常的不方便!
返回值:被删除的元素或者undefined
function myShift () {
if(!Array.isArray(this)) throw new Error('must be called by array');
const _tmp = [...this];
const len = this.length;
if(len === 0) return undefined;
this.length = 0;
for (let i = 1; i < len; i++) {
this[i-1] = _tmp[i];
}
return _tmp[0];
}
4.四句英语积累My name is (your full name) and I am the (job title) at (company name).I'm (first name) from (company name).I would like to talk to you about how our (producr/service) has helped our customers/clients to (a typical goal). (clients -- service)I'm reaching out to (the person's job title in the plural form) in the (name of industry/bussiness field) like you to let you know about our latest (name of product/service)