每日知识积累 Day 20

126 阅读9分钟

每日的知识积累,包括 1 个 Ts 类型体操,两个 Leetcode 算法题,三个前端八股文题,四个英语表达积累。

1. 一个类型体操

类型体操题目集合

Replace All

实现 ReplaceAll<S, From, To> 将一个字符串 S 中的所有子字符串 From 替换为 To。

分析

这个就是对字符串类型进行遍历。

尝试写出

type ReplaceAll<T extends string, A extends string, B extends string> = T extends `${infer F}${infer R}` ? (
    F extends A ? `${B}${ReplaceAll<R,A,B>}` : `${F}${ReplaceAll<R,A,B>}`
) : T;

测试用例

type replaced = ReplaceAll<'t y p e s', ' ', ''> // 期望是 'types'

参考答案

type ReplaceAll<S extends string, From extends string, To extends string> = From extends ''
  ? S
  : (S extends `${infer L}${From}${infer R}`
    ? `${ReplaceAll<L, From, To>}${To}${ReplaceAll<R, From, To>}`
    : S);

再来一个类似的类型体操

Replace

实现 Replace<S, From, To> 将字符串 S 中的第一个子字符串 From 替换为 To 。

分析

这次的话就是第一次就停了,我们不用遍历了,可以使用更加简单的方式。

尝试写出

type Replace<T extends string, A extends string, B extends string> = T extends `${infer F}${A}${infer L}` ? `${F}${B}${L}` : T;

测试用例

type replaced = Replace<'types are fun!', 'fun', 'awesome'> // 期望是 'types are awesome!'

参考答案

type Replace<S extends string, From extends string, To extends string> = From extends ''
  ? S
  : (S extends `${infer L}${From}${infer R}`
    ? (`${L}${To}${R}`)
    : S);

经验总结

  1. 注意这里的标准答案对 '' 做了特殊处理,确实比较严谨。

2. 四个 Leetcode 题目

刷题的顺序参考这篇文章 LeeCode 刷题顺序

2.1 [506] 相对名次

给你一个长度为 n 的整数数组 score ,其中 score[i] 是第 i 位运动员在比赛中的得分。所有得分都 互不相同 。

运动员将根据得分 决定名次 ,其中名次第 1 的运动员得分最高,名次第 2 的运动员得分第 2 高,依此类推。运动员的名次决定了他们的获奖情况:

名次第 1 的运动员获金牌 "Gold Medal" 。
名次第 2 的运动员获银牌 "Silver Medal" 。
名次第 3 的运动员获铜牌 "Bronze Medal" 。
从名次第 4 到第 n 的运动员,只能获得他们的名次编号(即,名次第 x 的运动员获得编号 "x")。
使用长度为 n 的数组 answer 返回获奖,其中 answer[i] 是第 i 位运动员的获奖情况。

 

示例 1:

输入:score = [5,4,3,2,1]
输出:["Gold Medal","Silver Medal","Bronze Medal","4","5"]
解释:名次为 [1st, 2nd, 3rd, 4th, 5th] 。
示例 2:

输入:score = [10,3,8,9,4]
输出:["Gold Medal","5","Bronze Medal","Silver Medal","4"]
解释:名次为 [1st, 5th, 3rd, 2nd, 4th] 。
 

提示:

n == score.length
1 <= n <= 104
0 <= score[i] <= 106
score 中的所有值 互不相同

尝试实现:

/**
 * @param {number[]} score
 * @return {string[]}
 */
var findRelativeRanks = function(score) {
    const sorted = [...score];
    
    score.sort((a,b)=>b-a);

    score.forEach((v, i)=> {
        const idx = sorted.findIndex(u => u===v);
        let now = i+1+"";
        if(i===0) now="Gold Medal";
        if(i===1) now="Silver Medal";
        if(i===2) now="Bronze Medal";
        sorted[idx] = now;
    })
    return sorted;
};

我的思路:

  1. 先复制一份,然后排序,最后查找变值即可。

得分结果: 6.42% 59.64%

总结提升:

  1. 数组上面的 sort 函数会改变原来的数组。
  2. 在 sort 之前使用 [...] 保留一份副本。

2.2 [535] TinyURL 的加密与解密

TinyURL 是一种 URL 简化服务, 比如:当你输入一个 URL https://leetcode.com/problems/design-tinyurl 时,它将返回一个简化的URL http://tinyurl.com/4e9iAk 。请你设计一个类来加密与解密 TinyURL 。

加密和解密算法如何设计和运作是没有限制的,你只需要保证一个 URL 可以被加密成一个 TinyURL ,并且这个 TinyURL 可以用解密方法恢复成原本的 URL 。

实现 Solution 类:

Solution() 初始化 TinyURL 系统对象。
String encode(String longUrl) 返回 longUrl 对应的 TinyURL 。
String decode(String shortUrl) 返回 shortUrl 原本的 URL 。题目数据保证给定的 shortUrl 是由同一个系统对象加密的。
 

示例:

输入:url = "https://leetcode.com/problems/design-tinyurl"
输出:"https://leetcode.com/problems/design-tinyurl"

解释:
Solution obj = new Solution();
string tiny = obj.encode(url); // 返回加密后得到的 TinyURL 。
string ans = obj.decode(tiny); // 返回解密后得到的原本的 URL 。
 

提示:

1 <= url.length <= 104
题目数据保证 url 是一个有效的 URL

尝试完成:

/**
 * Encodes a URL to a shortened URL.
 *
 * @param {string} longUrl
 * @return {string}
 */
var encode = function(longUrl) {
    return encodeURI(longUrl)
};

/**
 * Decodes a shortened URL to its original URL.
 *
 * @param {string} shortUrl
 * @return {string}
 */
var decode = function(shortUrl) {
    return decodeURI(shortUrl)
};

/**
 * Your functions will be called as such:
 * decode(encode(url));
 */

我的思路:

  1. 使用内置的加解密算法即可。

得分结果: 85.19% 85.19%

2.3 [299] 猜数字游戏

你在和朋友一起玩 猜数字(Bulls and Cows)游戏,该游戏规则如下:

写出一个秘密数字,并请朋友猜这个数字是多少。朋友每猜测一次,你就会给他一个包含下述信息的提示:

猜测数字中有多少位属于数字和确切位置都猜对了(称为 "Bulls",公牛),
有多少位属于数字猜对了但是位置不对(称为 "Cows",奶牛)。也就是说,这次猜测中有多少位非公牛数字可以通过重新排列转换成公牛数字。
给你一个秘密数字 secret 和朋友猜测的数字 guess ,请你返回对朋友这次猜测的提示。

提示的格式为 "xAyB" ,x 是公牛个数, y 是奶牛个数,A 表示公牛,B 表示奶牛。

请注意秘密数字和朋友猜测的数字都可能含有重复数字。

 

示例 1:

输入:secret = "1807", guess = "7810"
输出:"1A3B"
解释:数字和位置都对(公牛)用 '|' 连接,数字猜对位置不对(奶牛)的采用斜体加粗标识。
"1807"
  |
"7810"
示例 2:

输入:secret = "1123", guess = "0111"
输出:"1A1B"
解释:数字和位置都对(公牛)用 '|' 连接,数字猜对位置不对(奶牛)的采用斜体加粗标识。
"1123"        "1123"
  |      or     |
"0111"        "0111"
注意,两个不匹配的 1 中,只有一个会算作奶牛(数字猜对位置不对)。通过重新排列非公牛数字,其中仅有一个 1 可以成为公牛数字。
 

提示:

1 <= secret.length, guess.length <= 1000
secret.length == guess.length
secret 和 guess 仅由数字组成

尝试完成:

/**
 * @param {string} secret
 * @param {string} guess
 * @return {string}
 */
var getHint = function(secret, guess) {
    const _s = secret.split("");
    const _g = guess.split("");

    const n = Math.min(_s.length,_g.length);
    let A = 0;

    for(let i = 0; i < n; i++) {
        if(_s[i] === _g[i]){
            A++;
            _s[i] = null;
            _g[i] = null;
        }
    }

    let B = 0;
    const reds = {};
    const redg = {};
    for(let i = 0; i < n; i++) {
        const scur = _s[i];
        if( scur !== null){
            if(typeof reds[scur] === 'undefined'){
                reds[scur] = 1;
            } else {
                reds[scur] += 1;
            }
        }
        const gcur = _g[i];
        if( gcur !== null){
            if(typeof redg[gcur] === 'undefined'){
                redg[gcur] = 1;
            } else {
                redg[gcur] += 1;
            }
        }
    }

    for(let j in redg) {
        if(typeof reds[j] !== "undefined") {
            B += Math.min(redg[j], reds[j]);
        }
    }


    return `${A}A${B}B`
};

我的思路:

  • 先变成数组,这样的话就可以使用已有的 api 了
  • 获取最小长度,进行遍历
  • 将数字对,位置也对的筛选出去
  • 统计剩余数组中的字符,取最小值相加

得分结果: 46.91% 25.92%

**总结提升:**将字符串变成数组的代价有点大,需要更好的方式。

2.4 [412] Fizz Buzz

给你一个整数 n ,找出从 1 到 n 各个整数的 Fizz Buzz 表示,并用字符串数组 answer(下标从 1 开始)返回结果,其中:

answer[i] == "FizzBuzz" 如果 i 同时是 3 和 5 的倍数。
answer[i] == "Fizz" 如果 i 是 3 的倍数。
answer[i] == "Buzz" 如果 i 是 5 的倍数。
answer[i] == i (以字符串形式)如果上述条件全不满足。
 

示例 1:

输入:n = 3
输出:["1","2","Fizz"]
示例 2:

输入:n = 5
输出:["1","2","Fizz","4","Buzz"]
示例 3:

输入:n = 15
输出:["1","2","Fizz","4","Buzz","Fizz","7","8","Fizz","Buzz","11","Fizz","13","14","FizzBuzz"]

尝试完成:

/**
 * @param {number} n
 * @return {string[]}
 */
var fizzBuzz = function(n) {
    const rst = [];

    if(n===0) return rst;
    for(let i = 0; i < n; i++) {
        if((i+1) % 5 === 0 && (i+1) % 3 === 0) {
            rst[i] = "FizzBuzz";
            continue;
        }
        if((i+1) % 3 === 0) {
            rst[i] =  "Fizz";
            continue;
        }
        if((i+1) % 5 === 0) {
            rst[i] =  "Buzz";
            continue;
        }

        rst[i] = i+1+"";
    }
    return rst;
};

得分结果: 92.89% 27.56%

总结提升:

  1. 下标和自然数的关系要搞清楚。
  2. 先判断条件严苛的,后判断松的,就像设置路由一样。

3. 三个前端题目

  1. 实现Array.prototype.unshift

原理:unshift() 方法将一个或多个元素添加到数组的开头,并返回新数组的长度。

实现:分别对原来数组和新增加的元素序列进行遍历,前者的作用是为了挪动元素的顺序,后者的作用是为了插入新数据

返回值:变化之后的数组的长度

function myUnshift (...eles) {
    if(!Array.isArray(this)) throw new Error('must be called by array');
    const _count = eles?.length ?? 0;
    const _len = this.length;
    if(_count == 0) return _len;
    for (let i = 0; i < _len; i++) {
        this[i+_count] = this[i];
    }
    for (let i = 0; i < _count; i++) {
        this[i] = eles[i];
    }
    return this.length;
}
  1. 实现Array.prototype.slice

原理:slice() 方法返回原始数组的指定部分(浅拷贝),不修改原始数组。 实现:实现的难点在于对负号下标的处理上 返回值:返回切片元素组成的数组.

function mySlice (start, end) {
    if(!Array.isArray(this)) throw new Error('must be called by array');
    let _start = start === undefined ? 0 : start;
    let _end = end === undefined ? this.length : end;
    if(typeof _start !== 'number' || Number.isNaN(_start)) throw new Error('start must be number');
    if(typeof _end !== 'number' || Number.isNaN(_end)) throw new Error('end must be number');
    
    const _result = [];

    if (_start > this.length) {
        return _result; // 起始位置超出数组长度时返回空数组
    }

    if (_start < 0) {
        _start = Math.max(this.length + _start, 0); // 将负数的起始位置转换为正数
    }

    if(_end < 0) {
        _end = Math.max(this.length + _end, 0); // 将负数的终止位置转换为正数
    }
    if (_end > this.length) {
        _end = this.length; // 结束位置超出数组长度时将其修正为数组长度
    }

    if (_start > _end) {
        return _result;
    }

    
    for (let i = _start; i < _end; i++>) {
        _result.push(this[i]);
    }

    return _result;
}
  1. 实现Array.prototype.splice 原理:splice() 方法从数组中删除、替换或插入新的元素,并返回被删除的元素数组。

实现:使用一个临时变量保存原来数组中的元素,然后将其分成三个部分,然后按照要求拼接;需要注意的是:使用.length = 0来清除数组中的元素,而不要直接给数组赋值!

返回值:返回被删除的元素组成的数组

function mySplice (start, deleteCount = 0, ...items) {
    if(!Array.isArray(this)) throw new Error('must be called by array');
    let _tmp = [...this];
    // 从0到start-1
    const fragment1 = _tmp.slice(0, start-1);
    // 从start到deleteCount-1
    const fragment2 = _tmp.slice(start, deleteCount-1);
    // 从deleteCount到末尾
    const fragment3 = _tmp.slice(deleteCount);
    _tmp = [...fragment1, ...items, ...fragment3];
    this.length = 0;
    for (let i = 0; i < _tmp.length; i++) {
        this[i] = _tmp[i];
    }
    return fragment2;
}

4.四句英语积累

  1. I can understand that you're angry, but there's no need to get personal - I'm just tring to help.
  2. I completely understand why you're so upset, but I don't think it's fair to speak to me like that - I'm just doing my job.
  3. Could you please tone down your language, sir/madam?
  4. I don't want to help you, but I'm afraid I can't tolerate the kind of language you're using.