每日知识积累 Day 8

206 阅读7分钟

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

1. 一个类型体操

类型体操题目集合 Tuple To Object

传入一个元组类型,将这个元组类型转换为对象类型,这个对象类型的键/值都是从元组中遍历出来。

例如:

const tuple = ["tesla", "model 3", "model X", "model Y"] as const;

type result = TupleToObject<typeof tuple>; // expected { tesla: 'tesla', 'model 3': 'model 3', 'model X': 'model X', 'model Y': 'model Y'}

分析

元组类型的不好遍历,我们将其先转成联合类型然后使用 in 操作符进行遍历。

尝试写出

type TupleToObject<T extends any[]> = {
  [P in T[number]]: P;
};

测试用例

type C = TupleToObject<["name"]>; // {name: 'name'}

参考答案

type TupleToObject<T extends (string | number)[]> = { [P in T[number]]: P };

经验总结

注意这里不能写成 <T extends unknown[]> 只能写成 <T extends any[]> 否则 T[number] 会报错。由于 object 的键必须是 number string symbol 所以写的好一点的话:

type TupleToObject<T extends PropertyKey[]> = {
  [P in T[number]]: P;
};

2. 两个 Leetcode 题目

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

2.1 [419] 棋盘上的战舰

给你一个大小为 m x n 的矩阵 board 表示棋盘,其中,每个单元格可以是一艘战舰 'X' 或者是一个空位 '.' ,返回在棋盘 board 上放置的 舰队 的数量。

舰队 只能水平或者垂直放置在 board 上。换句话说,舰队只能按 1 x k(1 行,k 列)或 k x 1(k 行,1 列)的形状放置,其中 k 可以是任意大小。两个舰队之间至少有一个水平或垂直的空格分隔 (即没有相邻的舰队)。

输入:board = [["X",".",".","X"],[".",".",".","X"],[".",".",".","X"]]
输出:2
示例 2:

输入:board = [["."]]
输出:0


提示:

m == board.length
n == board[i].length
1 <= m, n <= 200
board[i][j] 是 '.' 或 'X'


进阶:你可以实现一次扫描算法,并只使用 O(1) 额外空间,并且不修改 board 的值来解决这个问题吗?

尝试实现:

/**
 * @param {character[][]} board
 * @return {number}
 */
var countBattleships = function (board) {
  const row = board.length;
  const col = board[0].length;
  let count = 0;

  for (let i = 0; i < row; i++) {
    for (let j = 0; j < col; j++) {
      if (
        board[i][j - 1] !== "X" &&
        (!board[i - 1] || (board[i - 1] && board[i - 1][j] !== "X")) &&
        board[i][j] === "X"
      ) {
        count++;
      }
    }
  }
  return count;
};

我的思路: 思路是看解析来的,遍历每个元素只要上方或者左边元素存在并且不等于 “X” 然后这个元素本身就是 “X” 则说明这个元素可以作为战舰的 “头” 有多少战舰头就有多少艘战舰。

得分结果: 5.36% 24.20%

总结提升:

  1. 首先,题目错误太多,导致根本看不懂;看不懂的时候看英文原题。
  2. 其次,将问题转化成更加简单的等价信息非常重要,其实这个题并不难。

2.2 [661] 图片平滑器

图像平滑器 是大小为 3 x 3 的过滤器,用于对图像的每个单元格平滑处理,平滑处理后单元格的值为该单元格的平均灰度。

每个单元格的  平均灰度 定义为:该单元格自身及其周围的 8 个单元格的平均值,结果需向下取整。(即,需要计算蓝色平滑器中 9 个单元格的平均值)。

如果一个单元格周围存在单元格缺失的情况,则计算平均灰度时不考虑缺失的单元格(即,需要计算红色平滑器中 4 个单元格的平均值)。

给你一个表示图像灰度的 m x n 整数矩阵 img ,返回对图像的每个单元格平滑处理后的图像 。

示例 1
输入:img = [[1,1,1],[1,0,1],[1,1,1]]
输出:[[0, 0, 0],[0, 0, 0], [0, 0, 0]]
解释:
对于点 (0,0), (0,2), (2,0), (2,2): 平均(3/4) = 平均(0.75) = 0
对于点 (0,1), (1,0), (1,2), (2,1): 平均(5/6) = 平均(0.83333333) = 0
对于点 (1,1): 平均(8/9) = 平均(0.88888889) = 0

示例 2
输入: img = [[100,200,100],[200,50,200],[100,200,100]]
输出: [[137,141,137],[141,138,141],[137,141,137]]
解释:
对于点 (0,0), (0,2), (2,0), (2,2): floor((100+200+200+50)/4) = floor(137.5) = 137
对于点 (0,1), (1,0), (1,2), (2,1): floor((200+200+50+200+100+100)/6) = floor(141.666667) = 141
对于点 (1,1): floor((50+200+200+200+200+100+100+100+100)/9) = floor(138.888889) = 138

提示:

m == img.length
n == img[i].length
1 <= m, n <= 200
0 <= img[i][j] <= 255

尝试完成:

/**
 * @param {number[][]} img
 * @return {number[][]}
 */
var imageSmoother = function (img) {
  const row = img.length;
  const img2 = [];

  for (let i = 0; i < row; i++) {
    const col = img[i].length;
    for (let j = 0; j < col; j++) {
      let count = 1;
      let sum = img[i][j];
      // 右
      if (img[i] && typeof img[i][j + 1] !== "undefined") {
        sum += img[i][j + 1];
        count++;
      }
      // 左
      if (img[i] && typeof img[i][j - 1] !== "undefined") {
        sum += img[i][j - 1];
        count++;
      }
      // 下
      if (img[i + 1] && typeof img[i + 1][j] !== "undefined") {
        sum += img[i + 1][j];
        count++;
      }
      // 上
      if (img[i - 1] && typeof img[i - 1][j] !== "undefined") {
        sum += img[i - 1][j];
        count++;
      }
      // 上左
      if (img[i - 1] && typeof img[i - 1][j - 1] !== "undefined") {
        sum += img[i - 1][j - 1];
        count++;
      }
      // 上右
      if (img[i - 1] && typeof img[i - 1][j + 1] !== "undefined") {
        sum += img[i - 1][j + 1];
        count++;
      }
      // 下左
      if (img[i + 1] && typeof img[i + 1][j - 1] !== "undefined") {
        sum += img[i + 1][j - 1];
        count++;
      }
      // 下右
      if (img[i + 1] && typeof img[i + 1][j + 1] !== "undefined") {
        sum += img[i + 1][j + 1];
        count++;
      }

      if (img2[i]) {
        img2[i][j] = Math.floor(sum / count);
      } else {
        img2[i] = [];
        img2[i][j] = Math.floor(sum / count);
      }
    }
  }

  return img2;
};

**我的思路:**这不需要什么高深的思路,但是下笔的时候需要考虑很多语法层面的东西,多维数组的可访问性很容易出错。

得分结果: 19.70% 13.63%

3. 三个前端题目

  1. 手写一个函数,实现 Function.prototype.call 功能。
  • 手写 call apply bind 的核心在于 js 中的一个机制:
  • 如果typeof a.b === "function"; 并且 a.b 是一个普通函数,则在执行a.b() 的时候,函数体中的 this 会指向 a
  • 不论是手写这三个中的哪一个,依据的核心都是上面的这个机制,只是使用的方式略微有些不同罢了!

此外,如果使用低版本的 js 写这三个函数,可能会碰到两个难题:

  • 如何获取剩余参数
  • 如何产生一个独一无二的属性名
  • 本文使用 ES6 语法书写了这三个方法,同时将上面两个难点的解决方法放在了最后。
function myCall(context, ...args: any) {
  // 检验
  if (typeof this !== "function")
    throw new Error("myCall must be called by a function");
  // 使用a.b机制改变this
  const _context = context || window;
  const _prop = Symbol("b");
  _context[_prop] = this;
  // 执行
  const result = _context[_prop](...args);
  // 删除
  delete _context[_prop];
  // 返回
  return result;
}

// Function.prototype.myCall = myCall
  1. 手写一个函数,实现 Function.prototype.apply 功能。 和 myCall 唯一区别是参数的类型是不是以数组的形式传递进来的.
function myApply(context, args: any[]) {
  // 检验
  if (typeof this !== "function")
    throw new Error("myApply must be called by a function");
  // 使用a.b机制改变this
  const _context = context || window;
  const _prop = Symbol("b");
  _context[_prop] = this;
  // 执行
  const result = args ? _context[_prop](...args) : _context[_prop]();
  // 删除
  delete _context[_prop];
  // 返回
  return result;
}

// Function.prototype.myApply = myApply

**等价操作:**下面两个操作是等价的

ES6: myFunction(...myArray); ES5: myFunction.apply(null, myArray);

  1. 手写一个函数,实现 Function.prototype.bind 功能。
  • 区别在于:会形成一个闭包,返回的是一个函数
  • 执行和删除的时机也不同
  • 带一点柯里化的味道
function myBind(context, ...args: any) {
  // 检验
  if (typeof this !== "function")
    throw new Error("myBind must be called by a function");
  // 使用a.b机制改变this
  const _context = context || window;
  const _prop = Symbol("b");
  _context[_prop] = this;
  return function (...rest: any) {
    // 合并参数
    const parm = [...args, ...rest];
    // 执行
    const result = _context[_prop](...parm);
    // 删除
    delete _context[_prop];
    // 返回
    return result;
  };
}

// Function.prototype.myBind = myBind

4.四句英语积累

  1. run out of something -- nothing left
    • The printer [has run out of ink]. Can you change the cartridge please?
    • We only have a couple of weeks to finish the project - we['re running out of time]!
  2. figure something out -- to finally understand or find the solution after a lot of thought
    • It took me a few days to [figure out how to use] the new software.
    • [He's checked the machine 3 times. but he still can't figure out why it's not working].