• 作者:老汪软件技巧
  • 发表时间: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)