likes
comments
collection
share

我用TypeScript这门语言刷leetcode的一些使用心得

作者站长头像
站长
· 阅读数 74

前言

承接上文🚀使用TypeScript刷LeetCode,前端同学刷题新体验

我用VSCode搭建了TypeScript刷题环境。然后我想分享下我用TypeScript这门语言刷leetcode的一些使用心得和注意事项。

首先,TypeScript静态类型语言,具有静态类型检查,但它不同于老派的的语言c++java

c++java强制的显式类型声明,但ts有强大的类型推断能力,更像是动静结合,这既能提升开发效率,也能保证类型安全。不过这也让TypeScript使用起来多了一一点难度,尤其是用好它。

类型注解和类型推导

我觉得刚开始学TypeScript最容易用错的就是类型注解类型推断TypeScript类型知识体系相当复杂,但我们刷算法用不到这么多,会些基本的就行了。

目前,JavaScript有八种基本数据类型number,string,boolan,null,undefined,symbol,bigint,objectTypeScriptany,never等等更多。

但我们不关心TypeScript多出来那些类型,因为我们刷题用不到,其中除了**obejct类型是复杂数据类型**,也就是引用数据类型,其余都是简单数据类型,像Array,Set,Map都是object类型,他们是通过原型链继承object,也可以说它们是object子集

刚开始用ts的时候,都有这样一个疑问?ts类型注解,但它可以加,也可以不加,那我到底是加还是不加呢???

或者说什么时候应该加类型注解,我刚开始也有这样的疑问。

我对此做出了一些总结:

🔖Note

在**TypeScript中,如果声明了一个变量(let)或者常量(const),是否加类型注解**,我总结了大情况:

  • 函数参数必须加类型注解

  • 函数返回值选择性加类型注解

  • 简单数据类型赋初始值不需要加类型注解

  • 复杂数据类型赋初始值依情况加类型注解

  • 未赋初始值必须加类型注解

一句话总结:能推断出类型的就不加,不能的才加

简单数据类型

很多人刚开始用ts的时候,会用写传统强类型语言c++java思维来写tsc++java强制的显式类型声明,然后写成这样:

let num: number = 1;
let num2: number = num
let str: string = "abc";
let flag: boolan = true;

这样真的超麻烦好吧,直接劝退。。。

我用TypeScript这门语言刷leetcode的一些使用心得

我用TypeScript这门语言刷leetcode的一些使用心得

ts有着类型推导能力,像这种简单数据类型,并且赋初始值的,我们不需要加类型注解

正确写法

let num = 1
let num2 = num
let str = "abc"
let flag = true

复杂数据类型

复杂数据类型我们以数组为例,我们依情况加类型注解

// 初始值是非空数据,ts会推断成 number[], 可以不用加类型注解
const arr1 = [1, 2]

// 初始值为空数组,ts会推断推断成 any[],需要加类型注解
const arr2 = []
// 正确写法
const arr3: number[] = []

我用TypeScript这门语言刷leetcode的一些使用心得

详细的可以看我后面数组的讲解。

函数参数

函数参数是必须加类型注解的,否则谁也不知道函数该传什么东西,像js没有类型注解,只能加一大坨JSDoc,比类型注解麻烦的多

/**
 * 两数之和
 * @param {number[]} nums 
 * @param {number} target 
 * @returns number[]
 */
function twoSum(nums, target) {
  
}
function twoSum(nums: number[], target: number) {
  
}

函数返回值

ts自动类型推导是可以推断出一个函数的函数参数的,那我们要不要加呢?,如果是在开发中,我都是不加的,但在刷算法时,我是建议加的,能够保证函数输出的正确性,leetcode上也是加的

function twoSum(nums: number[], target: number): number[] {
  
}

未赋初始值

未赋初始值一定要加类型注解!,否则会被推断成any类型

// 错误写法
let num
let str
const arr

// 正确写法
let num: number
let str: string
const arr: number[]

数组

数组object类型的一种,属于复杂数据类型,我们以实际代码来讲解:

创建数组

// 常见的错误写法,arr的类型类型会被推断成 any[],开发项目可以这么写,刷算法不要这么写。
const arr = [];

// 正确的写法
const arr: number[] = [];
// 联合类型
const arr2: 0 | 1 = []
const arr3: (number | string)[] = []
// 声明一个空的二维数组
const arr: string[][] = [];
// 或者这样,我一般喜欢前一种写法,简洁一点
const arr = new Array<number>();
const arr = new Array<number[]>();
// 多此一举
const arr: number[] = new Array<number>();

// 如果赋值不是空数组,这里会被推断成 number[],可以不加类型注解
const arr = [1, 2, 3];

指定长度的数组

创造指定长度的数组,这是很多人都不知道的东西,但在算法题中却很常见,这里来讲一下

// 创建一个数组并指定长度,必须使用Array来创建,这里创建了一个长度为5的数组
const arr = new Array<number>(5);
// 也可以指定长度并赋初始值,fill为每个元素的初始值
const arr = new Array<number>(5).fill(0); // [0, 0, 0, 0, 0]

创建一个指定长度的二维数组,很多人又不知道了。

fill有局限性,当你给 fill 传递一个入参时,如果这个入参的类型是引用类型,那么 fill 在填充坑位时填充的其实就是入参的引用

大家可以自己试一下

const arr = new Array<number[]>(5).fill(new Array<number>(5).fill(0));
arr[0][0] = 1;
console.log(arr);

我用TypeScript这门语言刷leetcode的一些使用心得

js/ts创建一个指定大小的二维数据确实不好搞,代码看起来也比较恶心

正确的做法:


// 使用for循环,for循环性能比map要高,就是麻烦
const arr = new Array<number[]>(5);
for (let i = 0; i < arr.length; i++) {
  arr[i] = new Array<number>(5).fill(0);
}
arr[0][0] = 1;

// 使用map,可以一行搞定
const arr = new Array<number[]>(5).fill([]).map(() => new Array<number>(5).fill(0));
arr[0][0] = 1;

我用TypeScript这门语言刷leetcode的一些使用心得

Set和Map

tsSetMap,也是复杂数据类型,在数据结构中也叫做哈希表,它们使用起来和js还是有些区别的,声明一个SetMap需要泛型去指定类型,当然,有些时候你不指定也行,不想人为的去思考它的类型推断,我建议你加上。

// 声明一个Set,储存的是number类型数据
const set1 = new Set<number>();
// 也可以这样
const set2: Set<number> = new Set();
// 多此一举,不建议
const set3: Set<number> = new Set<number>();
// 也可以是自己创建的类型,前提是有
const listNodeSet = new Set<ListNode>();


// map跟对象很像,用键值对存储
const map = new Map<string, number>();

// 如果有初始值,可以不加泛型,也可以加,代码风格也统一
const hashMap = new Map([
  ["(", ")"],
  ["[", "]"],
  ["{", "}"],
]);

其他常见数据结构

刷算法的话,不需要完整实现数据结构的所有操作,我们只要定义基本的数据结构即可

栈与队列

js/ts中数组是个奇特的存在,不仅仅可以当数组,也可用用作栈和队列,它本身很像一个双端队列

// 用数组模拟栈
const stack: number[] = []

// 使用push, pop来模拟进栈和出栈
stack.push(1) // 进栈
stack.push(2) // 进栈
console.log(stack[stack.length - 1]) // 获取栈顶元素:2
stack.pop() // 出栈 2
stack.pop() // 出栈 1
console.log(stack.length === 0) // true 栈空

// 用数组模拟队列
const queue: number[] = []

// 使用push, shift来模拟进队和出队
queue.push(1) // 进队
queue.push(2) // 进队
queue[0] // 获取队头元素
queue.shift() // 出队 1
queue.shift() // 出队 2
console.log(queue.length === 0) // true 队空

单链表

leetcode上的写法不一样,我觉得我的写法要好点

/**
 * 单链表节点类
 */
class ListNode {
  public val: number;
  public next: ListNode | null;
  constructor(val: number, next: ListNode | null = null) {
    this.val = val;
    this.next = next;
  }
}

const l1 = new ListNode(1)
l1.next = new ListNode(2)

二叉树

/**
 * 二叉树节点类
 */
class TreeNode {
  public val: number;
  public left: TreeNode | null;
  public right: TreeNode | null;
  constructor(val: number, left: TreeNode | null = null, right: TreeNode | null = null) {
    this.val = val;
    this.left = left;
    this.right = right;
  }
}

// 还有一种写法
class TreeNode {
  constructor(
    public val: number,
    public left: TreeNode | null = null,
    public right: TreeNode | null = null
  ) {
    this.val = val;
    this.left = left;
    this.right = right;
  }
}

其他之类的数据结构一般不会考,我也没去写过。

刷题注意事项

我给刷算法题的同学做个建议,我们做算法题的时候不要过分的去炫技,不要说要写的多么多么复杂,多么难懂,才显得自己牛逼,毕竟代码是给人看的,给面试官看的,当然也不要写的过于low,我们的代码应该在易用性可读性当中进行权衡

还有,尽量不要去调用语言自带的api,像我们遍历,用最朴素的forwhile就行了,像数组filterforEachmap能不用就不要去用,而且光从性能上讲,for性能相比于forEachmap是最好的,我们算法注重的是时间复杂度。当然在开发中,这些都随便用。

最后

最后,分享下自己的刷题仓库:github.com/xiaodye/typ…

如果有什么疑问或错误,欢迎在评论区讨论和指正🥰🥰

转载自:https://juejin.cn/post/7195471304152055867
评论
请登录