我用TypeScript这门语言刷leetcode的一些使用心得
前言
承接上文:🚀使用TypeScript刷LeetCode,前端同学刷题新体验
我用VSCode
搭建了TypeScript
刷题环境。然后我想分享下我用TypeScript
这门语言刷leetcode
的一些使用心得和注意事项。
首先,TypeScript
是静态类型语言,具有静态类型检查,但它不同于老派的的语言c++
、java
,
c++
、java
是强制的显式类型声明,但ts
有强大的类型推断能力,更像是动静结合,这既能提升开发效率,也能保证类型安全。不过这也让TypeScript
使用起来多了一一点难度,尤其是用好它。
类型注解和类型推导
我觉得刚开始学TypeScript
最容易用错的就是类型注解和类型推断,TypeScript
的类型知识体系相当复杂,但我们刷算法用不到这么多,会些基本的就行了。
目前,JavaScript
有八种基本数据类型:number
,string
,boolan
,null
,undefined
,symbol
,bigint
,object
,TypeScript
有any
,never
等等更多。
但我们不关心TypeScript
多出来那些类型,因为我们刷题用不到,其中除了**obejct
类型是复杂数据类型**,也就是引用数据类型,其余都是简单数据类型,像Array
,Set
,Map
都是object
类型,他们是通过原型链继承object
,也可以说它们是object
的子集。
刚开始用ts
的时候,都有这样一个疑问?ts
有类型注解,但它可以加,也可以不加,那我到底是加还是不加呢???
或者说什么时候应该加类型注解,我刚开始也有这样的疑问。
我对此做出了一些总结:
🔖Note:
在**
TypeScript
中,如果声明了一个变量(let
)或者常量(const
),是否加类型注解**,我总结了五大情况:
函数参数,必须加类型注解
函数返回值,选择性加类型注解
简单数据类型,赋初始值,不需要加类型注解
复杂数据类型,赋初始值,依情况加类型注解
未赋初始值,必须加类型注解
一句话总结:能推断出类型的就不加,不能的才加
简单数据类型
很多人刚开始用ts
的时候,会用写传统强类型语言c++
、java
的思维来写ts
,c++
和java
是强制的显式类型声明,然后写成这样:
let num: number = 1;
let num2: number = num
let str: string = "abc";
let flag: boolan = true;
这样真的超麻烦好吧,直接劝退。。。
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[] = []
详细的可以看我后面数组的讲解。
函数参数
函数参数是必须加类型注解的,否则谁也不知道函数该传什么东西,像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);
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;
Set和Map
ts
中Set
和Map
,也是复杂数据类型,在数据结构中也叫做哈希表,它们使用起来和js
还是有些区别的,声明一个Set
或Map
需要泛型去指定类型,当然,有些时候你不指定也行,不想人为的去思考它的类型推断,我建议你加上。
// 声明一个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
,像我们遍历,用最朴素的for
、while
就行了,像数组的filter
、forEach
、map
能不用就不要去用,而且光从性能上讲,for
性能相比于forEach
、map
是最好的,我们算法注重的是时间复杂度。当然在开发中,这些都随便用。
最后
最后,分享下自己的刷题仓库:github.com/xiaodye/typ…
如果有什么疑问或错误,欢迎在评论区讨论和指正🥰🥰
转载自:https://juejin.cn/post/7195471304152055867