每日知识积累 Day 2

190 阅读7分钟

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

1. 一个类型体操

Concat

在类型系统里实现 JavaScript 内置的 Array.concat 方法,这个类型接受两个参数,返回的新数组类型应该按照输入参数从左到右的顺序合并为一个新的数组。

例如:

type Result = Concat<[1], [2]>; // expected to be [1, 2]

分析

这有啥难的,Ts 中提供了扩展运算符,因此只要验证类型是正确的,没有什么难的吧!

尝试写出

type Concat<T extends unknown[], K extends unknown[]> = T extends unknown[]
  ? K extends unknown[]
    ? [...T, ...K]
    : never
  : never;

测试用例

type C = Concat<["a", 1], [2, 3]>; // ['a', 1, 2, 3]

参考答案

type Concat<T extends unknown[], U extends unknown[]> = [...T, ...U];

经验总结

做题的时候先限制类型,不推断的化无需使用 extends.

2. 两个 Leetcode 题目

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

2.1 [414] Third Maximum Number

Example 1:

Input: nums = [3,2,1]
Output: 1
Explanation:
The first distinct maximum is 3.
The second distinct maximum is 2.
The third distinct maximum is 1.
Example 2:

Input: nums = [1,2]
Output: 2
Explanation:
The first distinct maximum is 2.
The second distinct maximum is 1.
The third distinct maximum does not exist, so the maximum (2) is returned instead.
Example 3:

Input: nums = [2,2,3,1]
Output: 1
Explanation:
The first distinct maximum is 3.
The second distinct maximum is 2 (both 2's are counted together since they have the same value).
The third distinct maximum is 1.


Constraints:

1 <= nums.length <= 10^4
-2^31 <= nums[i] <= 2^31 - 1

Follow up: Can you find an O(n) solution?

尝试实现:

/**
 * @param {number[]} nums
 * @return {number}
 */
var thirdMax = function (nums) {
  var length = nums.length;
  var first = null;
  var second = null;
  var third = null;

  function getMax(num) {
    if (num == first || num == second || num == third) {
      return;
    }

    if (first === null) {
      first = num;
      return;
    } else if (num > first) {
      third = second;
      second = first;
      first = num;
      return;
    }

    if (second === null) {
      second = num;
      return;
    } else if (num > second) {
      third = second;
      second = num;
      return;
    }

    if (third === null) {
      third = num;
      return;
    } else if (num > third) {
      third = num;
      return;
    }
  }

  for (var i = 0; i < length; i++) {
    getMax(nums[i]);
  }

  if (third !== null) return third;

  return first;
};

console.log(thirdMax([3, 3, 4, 3, 4, 3, 0, 3, 3])); // 0

我的思路:

  1. 数字相关的,不要用 if(num) 因为 num 可能是 0 这样做一定会导致错误,直接用 if(num === null)`` 或者 if(num !== null)` 即可。
  2. 数组降维的时候,如需去重使用如下的代码:
if (num == first || num == second || num == third) {
  return;
}
  1. 思路:先初始化三个变量保存前三,然后遍历数组,将每一个元素放到合适的位置即可,每一个元素不是最大就是次大要么就是第三大,要么就啥也不是,当它是最大的时候,原来的最大变成第二大,第二大变成第三大,以此类推。
  2. 注意赋值顺序,别赋值完大家都变成一样的数字。

评分:48.69% 88.24%

2.2 [628] 三个数最大乘积

给你一个整型数组 nums ,在数组中找出由三个数组成的最大乘积,并输出这个乘积。

示例 1:

输入:nums = [1,2,3]
输出:6
示例 2:

输入:nums = [1,2,3,4]
输出:24
示例 3:

输入:nums = [-1,-2,-3]
输出:-6


提示:

3 <= nums.length <= 104
-1000 <= nums[i] <= 1000

尝试完成:

/**
 * @param {number[]} nums
 * @return {number}
 */
var maximumProduct = function (nums) {
  var length = nums.length;
  if (length < 3) return null;
  if (length == 3) return nums[0] * nums[1] * nums[2];

  nums.sort((a, b) => a - b);

  var a = nums[length - 1] * nums[length - 2] * nums[length - 3];
  var b = nums[0] * nums[1] * nums[length - 1];

  if (nums[length - 1] <= 0) return a;
  if (nums[length - 2] <= 0) return b;
  if (nums[length - 3] <= 0) return b;

  return a > b ? a : b;
};

我的思路:

  1. 数组长度小于 3 返回 null
  2. 数组长度等于 3 返回乘积
  3. 对数组排序
  4. 检查正数的个数: 最大值只可能有两种可能
    • 正数个数大于等于 3,返回最大三值乘积与最小值和最大两值乘积较大者。
    • 正数个数等于 2,返回最小两数和最大数乘积。
    • 正数个数等于 1,返回最小两数和最大数乘积。
    • 正数个数等于 0,返回最大三数乘积。

评分:30.92% 64.47%

思考:关键在于按照正数个数分类讨论的时候,能够快速认清最大值只有可能是两种情况。然后就是我们可以通过比较排序之后的最大值与 0 的情况快速判断出有几个正数;0 是干扰项,要认清楚 0 对于乘积的影响。

3. 三个前端题目

  1. typeof null 的结果是什么?为什么?
  • 计算结果为:"object",之所以出现这个结果,源自 js 这门编程语言设计之初的规定。
  • 在 js 第一版的时候,所有的数据都是存放在一个 32bit 的存储空间中,这 32bit 被划分成两个部分,一个是用来表示类型的,通常占 0-3 个 bit,剩下的部分则都是用来表示数据的。
  • 为什么是 0-3 个 bit 位表示类型呢?因为当时表示 int 类型的是 1,没错只有一位,所以对于整数来说剩下的 31 位都可以用来表示数据;而对于 string 类型,标识符则为 100,有三个 bit 位;double 类型的是 010;boolean 则为 110;而 object 的类型则是用 000 三个 bit 位进行表示;
  • 除了上面这些,undefined 用一个溢出的整数来表示,所以它没有标识位,为 0 位;而对于 null,js 简单粗暴的使用机器码 NULL,但是机器码 NULL 本身是 32 个 0。
  • 注意 32 个 0 意味着前三位也是 0,所以 typeof null 的时候 null 会被当成 object,所以结果返回的是 'object'
  1. 手写一个函数实现 instanceof 操作符的功能。
  • x instanceof y 的作用原理就是,在 x 的原型链上寻找 y,如果找到了就返回 true,如果找不到就返回 false;
  • 根据其作用原理,其实现过程也就呼之欲出了:首先判断 x 是不是 object 类型的,如果不是直接返回 false 就可以了。如果是的话,则逐级比较 x 的原型和 y 是否相等,直到 x 原型链的尽头,也就是 null。如果此时 y 与任何一层原型都不相等,则返回 false,否则返回 true。
  • 实现代码如下:
function myInstanceof(obj, cons) {
  if (Object(obj) !== obj) return false;
  let _p = obj.__proto__;
  while (_p !== null) {
    if (_p == cons.prototype) return true;
    _p = _p.__proto__;
  }
  return false;
}
  1. 手写一个函数,实现 new 操作符 根据 new 操作符的作用原理,可以比较方便的写出其实现:
new执行以下几个步骤:
1.  创建一个空对象:new 操作符会创建一个新的空对象,该对象继承自构造函数的原型对象。
2.  设置对象的原型链:新创建的空对象的 __proto__ 属性会被设置为构造函数的 prototype 属性,这样就建立了对象与构造函数原型之间的连接。
3.  执行构造函数代码:将新创建的空对象作为 this 关键字绑定到构造函数,并执行构造函数内部的代码。这样,构造函数可以通过 this 来操作和设置新对象的属性和方法。 
4.  返回新对象实例:如果构造函数没有显式返回值,则 new 操作符会隐式返回新创建的对象实例。如果构造函数有返回值,有以下两种情况:
    -   如果返回值是一个对象,则 new 操作符会返回该对象。
    -   如果返回值不是一个对象(比如基本数据类型),则忽略返回值,返回新创建的对象。

自己实现的 myNew 函数,接受一个 function 类型的参数作为其 "类",接受剩余参数作为返回对象的初始化值。

function myNew(constructor, ...rest) {
  // 判断传入的参数是否有效
  if (typeof constructor !== "function")
    throw new Error("参数一必须是构造函数");
  // 绑定原型
  const obj = Object.create(constructor.prototype);
  // 执行构造函数
  const rst = constructor.apply(obj, rest);
  return rst && typeof rst === "object" ? rst : obj;
}

4.四句英语积累

  1. identify
    • Have we identified the cause of the problem?
    • We're very excited because we've identified a gap in the market. [a gap in the market means an oppotunity]
  2. implement
    • I think we should implement flexitime to help our staff with their work-life balance.
    • We need to implement a new IT system as soon as possible.