面试复习篇1:数据结构与算法篇(上)
数据结构
|-对象
|-数组
|-栈
|-队列
|-链表
|-字典
|-集合
|-树
对象
const obj = {
name: 'ljx',
age: 28
}
// assign 赋值操作
const assignObj = Object.assign({}, obj, { position: 'FE' })
console.log(assignObj) // { name: "ljx", age: 28, position: "FE" }
// 获取键值对
const entries = Object.entries(assignObj)
console.log(entries) // [["name","ljx"],["age",28],["position","FE"]]
// 获取所有key
const keys = Object.keys(assignObj) // ["name","age","position"]
// 获取所有value
const values = Object.values(assignObj) // ["ljx", 28, "FE"]
// 判断是否是某个class的实例
const is = Object.is(entries, obj)
console.log(is) //false
// 属性描述符
const defineObj: Record<string, any> = Object.defineProperty(assignObj, 'gender', { writable: false, value: 'male' }) // { name: "ljx", age: 28, position: "FE", gender: 'male }
console.log(Object.is(defineObj, assignObj)) // true
console.log(defineObj === assignObj) // true
// 冻结操作
Object.freeze(defineObj)
defineObj.age = 18 // TypeError: Cannot assign to read only property 'age' of object '#<Object>'
console.log(defineObj)
Object.freeze()
的本质是将对象所有属性的writeable
描述符修改为false
数组
const arr: Array<any> = []
// push
console.log(arr.push(0, 1, 2, 3, 4, 5)) // 6
// pop
const last = arr.pop()
console.log(last) // 5
console.log(arr) // [0, 1, 2, 3, 4]
// shift
const first = arr.shift()
console.log(first) // 0
console.log(arr) // [1, 2, 3, 4]
// unshift
console.log(arr.unshift(0)) //5
// 获取数组长度
console.log(arr.length) // 5
// concat
console.log(arr.concat([5,6,7],8,[9,10])) // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
// reverse
console.log(arr.reverse()) // [4, 3, 2, 1, 0]
// map
console.log(arr.map(item => `${item}`)) // ['4', '3', '2', '1', '0']
// filter
console.log(arr.filter((item, index) => index===2)) // [2]
// some
console.log(arr.some((item) => {
if (typeof item !== 'number') return false
return true
})) // true
// every
console.log(arr.every((item) => typeof item === 'number' )) // true
// find
console.log(arr.find((item) => item === 3)) // 3
console.log(arr.find((item) => item === 5)) // undefined
// findIndex
console.log(arr.findIndex((item) => item === 4)) // 0
console.log(arr.findIndex((item) => item === 5)) // -1
// join
console.log(arr.join('-')) // 4-3-2-1-0
// slice
console.log(arr.slice(0, 2)) // [4, 3]
console.log(arr.slice(2)) // [2, 1, 0]
console.log(arr.slice(-2)) // [1, 0]
console.log(arr.slice()) // [4, 3, 2, 1, 0]
// splice
console.log(arr.splice(0, 2)) // [4, 3]
console.log(arr) // [2, 1, 0]
console.log(arr.splice(0, 0, 5)) // []
console.log(arr) // [5, 2, 1, 0]
// sort
console.log(arr.sort((a, b) => a - b)) // [0, 1, 2, 5]
console.log(arr.sort((a, b) => b - a)) // [5, 2, 1, 0]
console.log(arr) // [5, 2, 1, 0]
// reduce
console.log(arr.reduce((obj, value, index) => {
obj[`${index}`] = value
return obj
}, {})) // {0: 5, 1: 2, 2: 1, 3: 0}
// forEach
try {
arr.forEach((item, index) => {
console.log(item) // 5 2
if(index === 1) throw new Error('跳出forEach')
})
} catch (error) {
console.log(error)
}
// includes
console.log(arr.includes(5))
// indexOf
console.log(arr.indexOf(5, 1)) // -1
console.log(arr.indexOf(5, 0)) // 0
// lastIndexOf
console.log(arr.lastIndexOf(6, 1)) // -1
console.log(arr.lastIndexOf(5, 0)) // 0
// Array.from
const o = {
"0": 1,
"1": 2,
"5": 4,
length: 6
}
console.log(Array.from(o)) // [1, 2, undefined, undefined, undefined, 4]
// Array.isArray
console.log(Array.isArray(o)) // false
console.log(Array.isArray(arr)) // true
纯函数:sort、map、filter、concat、slice
字典
// 初始化
const map = new Map(Object.entries({ name: 'ljx', age: 18 }))
console.log(map) // Map(2) {size: 2, name => ljx, age => 18}
// set
console.log(map.set('gender', 'male')) // Map(3) {size: 3, name => ljx, age => 18, gender => male}
// get
console.log(map.get('gender')) // male
// size
console.log(map.size) // 3
// delete
console.log(map.delete('gender')) // true
// has
console.log(map.has('gender')) // false
// keys
console.log([...map.keys()]) // (2) ['name', 'age']
// values
console.log([...map.values()]) // (2) ['ljx', 18]
// entries
console.log([...map.entries()]) // [['name', 'ljx], ['age', 18]]
// clear
console.log(map.clear()) // undefined
console.log(map) // Map(0) {size: 0}
const weakmap = new WeakMap()
const container: Record<string, any> = {
key: {}
}
weakmap.set(container.key, 1)
container.key = null
console.log(weakmap) // WeakMap {{} => 1}
setTimeout(() => {
console.log(weakmap) // weakMap
}, 10000)
注意: 当发生GC
时,检测到weakMap
的key
对应的对象如果被回收了,会自动删除映射
集合
const set = new Set()
let i = 0
while (i < 10) {
set.add(i)
i++
}
i = 0
while (i < 10) {
set.add(i)
i++
}
set.add(1)
set.add(1)
set.add(1)
console.log(set.has(1)) // true
set.clear()
console.log(set)
// 数组去重
const arr = [1, 1, 2, 2, 3, 3, 4, 4]
console.log([...new Set(arr)]) // (4) [1, 2, 3, 4]
// weakSet
const weakSet = new WeakSet()
const obj: Record<string, any> = { test: {} }
weakSet.add(obj.test)
console.log(weakSet.has(obj.test)) // true
delete obj.test
console.log(weakSet.has(obj.test)) // false
setTimeout(() => {
console.log(weakSet) // 空weakSet
}, 5000)
栈
/**
* @description: 链表节点class
*/
class ListNode {
value: any
prev?: ListNode
next?: ListNode
constructor(value: any) {
this.value = value
}
}
class Stack {
index: number
list: Record<string, ListNode>
constructor() {
this.index = 0
this.list = {}
}
push(node: ListNode) {
this.list[this.index] = node
this.index++
}
pop() {
if(this.size() === 0) return undefined
const item = this.list[this.index - 1]
delete this.list[this.index - 1]
this.index--
return item
}
size() {
return this.index
}
clear() {
this.index = 0
this.list = {}
}
indexOf(position: number) {
return this.list[position]
}
insert(position: number, node: ListNode) {
if (position > this.index || position < 0) return false
if (position === this.index) {
this.list[position] = node
} else {
for (let i = 0; i < this.size() + 1; i++) {
if (i >= position) {
const temp = this.list[i]
this.list[i] = node
node = temp
}
}
}
this.index++
return true
}
}
const stack = new Stack()
// 初始化测试
console.log(stack.size())
console.log(stack.pop())
console.log(stack.indexOf(-999))
console.log(stack.indexOf(0))
console.log(stack.indexOf(1))
console.log(stack.insert(1, new ListNode(999)))
console.log(stack.insert(-1, new ListNode(999)))
console.log(stack.insert(0, new ListNode(999)))
console.log(stack.indexOf(0))
console.log(stack.index)
// 构造一个栈
let i = 0
while (i < 10) {
const node = new ListNode(i)
stack.push(node)
i++
}
// push测试
console.log(stack)
console.log(stack.size())
console.log(stack.index)
// pop测试
console.log(stack.pop())
console.log(stack.index)
console.log(stack.pop())
console.log(stack.index)
console.log(stack.pop())
console.log(stack.index)
console.log(stack.size())
console.log(stack.index)
// insert测试
stack.insert(3, new ListNode(300))
console.log(stack.size())
console.log(stack.index)
console.log(stack.list)
// indexOf测试
console.log(stack.indexOf(3))
// // clear测试
stack.clear()
console.log(stack)
队列
/**
* @description: 链表节点class
*/
class ListNode {
value: any
prev?: ListNode
next?: ListNode
constructor(value: any) {
this.value = value
}
}
class Queue {
index: number
headerIndex: number
list: Record<string, ListNode>
constructor() {
this.index = 0
this.headerIndex = 0
this.list = {}
}
enqueue(node: ListNode) {
this.list[this.index] = node
this.index++
}
dequeue() {
const item = this.list[this.headerIndex]
delete this.list[this.headerIndex]
this.headerIndex++
return item
}
size() {
return this.index - this.headerIndex
}
header() {
if(!this.size) return
return this.list[this.headerIndex]
}
tail() {
if(this.index === this.headerIndex) return undefined
return this.list[this.index - 1]
}
indexOf(position: number) {
// 判断是否越界
if (!this.size() || position <= 0 || this.size()< position) return undefined
const realIndex = this.headerIndex + position - 1
const item = this.list[realIndex]
return item
}
insert(position: number, node: ListNode) {
if (!this.size()) return false
// 判断是否越界
if (this.headerIndex > position || position > this.index) return false
if (position === this.index) {
this.list[position] = node
} else {
for (let i = this.headerIndex; i < this.index + 1; i++) {
if (position <= i) {
const temp = this.list[i]
this.list[i] = node
node = temp
}
}
}
this.index++
return true
}
clear() {
this.index = 0
this.headerIndex = 0
this.list = {}
}
}
// 构造一个队列
const queue = new Queue()
// 初始化测试
console.log(queue.size())
console.log(queue.header())
console.log(queue.tail())
// 入队测试
queue.enqueue(new ListNode(0))
console.log(queue.size())
console.log(queue.header())
console.log(queue.tail())
// 出队测试
queue.dequeue()
console.log(queue.size())
console.log(queue.header())
console.log(queue.tail())
console.log(queue.list)
// 连续入队测试
let i = 0
while (i < 10) {
queue.enqueue(new ListNode(i))
i++
}
console.log(queue.size())
console.log(queue.header())
console.log(queue.tail())
console.log(queue.list)
// inedexOf测试
// inedexOf获取左边越界
console.log(queue.indexOf(0))
// inedexOf获取队首
console.log(queue.indexOf(1))
// inedexOf获取中间
console.log(queue.indexOf(4))
// inedexOf获取队尾
console.log(queue.indexOf(10))
// inedexOf获取右边越界
console.log(queue.indexOf(11))
// 连续出队测试
queue.dequeue()
queue.dequeue()
queue.dequeue()
console.log(queue.size())
console.log(queue.header())
console.log(queue.headerIndex)
console.log(queue.index)
console.log(queue.tail())
链表
type INode = {
value: any,
next?: INode
prev?: INode
}
let doublyLinkedList: INode | undefined
let p : INode | undefined
// 组装双向链表
let i = 0
while (i < 10) {
const node = { value: i }
if (!p) {
p = node
doublyLinkedList = p
} else {
const prev = p
p.next = node
p = p.next
p.prev = prev
}
i++
}
// 遍历链表
p = doublyLinkedList
while (p) {
console.log(p.value) // 0,1,2,3,4,5,6,7,8,9
if(p.next == undefined) break
p = p.next
}
while (p) {
console.log(p.value) // 9,8,7,6,5,4,3,2,1,0
if (p.prev == undefined) break
p = p.prev
}
// 反转双向链表
p = doublyLinkedList
let j: INode | undefined
while (p) {
const nextNode = p.next
const prevNode = p.prev
p.next = prevNode
p.prev = nextNode
j = p
p = nextNode
}
// 遍历反转后的链表
while (j) {
console.log(j.value) // 9,8,7,6,5,4,3,2,1,0
if (j.next == undefined) break
j = j.next
}
while (j) {
console.log(j.value) // 0,1,2,3,4,5,6,7,8,9
if (j.prev == undefined) break
j = j.prev
}
// 插入操作,在4,5之间插入一个x
while (j) {
if (j.value === 5) {
const node: INode = { value: 'x' }
const nextNode = j.next
const prevNode = j
node.next = nextNode
nextNode!.prev = node
node.prev = prevNode
prevNode.next = node
}
if (j.next == undefined) break
j = j.next
}
while (j) {
console.log(j.value) // 0, 1, 2, 3, 4, x, 5, 6, 7, 8, 9
if (j.prev == undefined) break
j= j.prev
}
树
/**
* @description: 链表节点class
*/
class TreeNode {
value: any
left?: TreeNode
right?: TreeNode
constructor(value: any) {
this.value = value
}
}
/**
* @description: 创建二叉树
* @param {number} size
*/
function createTree(size: number) {
// 创建根节点
const tree = new TreeNode(0)
let i = 1
const queue = []
queue.push(tree)
while (i< size) {
const node = queue.shift() as TreeNode
if (!node.left) {
node.left = new TreeNode(i)
i++
queue.push(node)
}
if (!node.right && i< size) {
node.right = new TreeNode(i)
queue.push(node.left)
queue.push(node.right)
i++
}
}
return tree
}
const tree = createTree(20)
console.log(tree)
/**
* @description: 广度遍历二叉树
* @param {TreeNode} tree
*/
function breadthTraversal(tree: TreeNode) {
const queue = [tree]
while (queue.length) {
const node = queue.shift()
console.log(node?.value)
if (node?.left) queue.push(node.left)
if (node?.right) queue.push(node.right)
}
}
console.log('广度优先遍历')
breadthTraversal(tree) // 0, 1 ,2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,15,16,17, 18,19
/**
* @description: 深度遍历二叉树
* @param {TreeNode} tree
*/
function depthTraversal(tree: TreeNode) {
const stack = [tree]
while (stack.length) {
const node = stack.pop()
if (node) {
console.log(node.value)
if (node.right) stack.push(node.right)
if (node.left) stack.push(node.left)
}
}
}
console.log('深度优先遍历')
depthTraversal(tree)
/**
* @description: 前序遍历
* @param {TreeNode} tree
*/
function preorderTraversal(tree: TreeNode) {
console.log(tree.value)
if(tree.left) preorderTraversal(tree.left)
if(tree.right) preorderTraversal(tree.right)
}
console.log('前序遍历')
preorderTraversal(tree)
/**
* @description: 中序遍历
* @param {TreeNode} tree
*/
function inorderTraversal(tree: TreeNode) {
if(tree.left) inorderTraversal(tree.left)
console.log(tree.value)
if(tree.right) inorderTraversal(tree.right)
}
console.log('中序遍历')
inorderTraversal(tree)
/**
* @description: 后序遍历
* @param {TreeNode} tree
*/
function postorderTraversal(tree: TreeNode) {
if(tree.left) postorderTraversal(tree.left)
if(tree.right) postorderTraversal(tree.right)
console.log(tree.value)
}
console.log('后序遍历')
postorderTraversal(tree)
shift
、unshift
本身都是高时间复杂度的操作,应该是要用别的方式替换掉的
算法
|- 分而治之
|- 动态规划
|- 贪心
|- 回溯
排序算法
|- 冒泡
|- 快速
|- 插入
|- 归并
|- 选择
搜索算法
|- 顺序搜索
|- 二分搜索
总结
先纯印象,归纳出知识结构,然后按照结构默写,没有复习过,不借助任何外力,单纯凭脑海中的知识点复现,写时能明显感受到沉睡的知识点逐渐复苏变得清晰,也可以明确的知道自己记得牢靠的点有哪些,忘了的,模糊的点又有哪些,经过总结后面可以更直观的查缺补漏
-
完全遗忘: 排序算法、搜索算法、双栈实现一个队列、二叉树的规律,后面再补充实际应用场景
-
卡壳很久才写出来: 生成二叉树
-
有印象但感觉模糊的:iterator、generator;weakMap的弱引用和印象里有些差别
-
行云流水完成的: 栈、队列、链表(后面应该可以不用关心的)
分析
-
完全遗忘是因为知识点忘了,无法回想起来,补充完知识点应该能做出来一部分,比如排序,只记得名字是什么,忘了每种排序的知识点分别是什么
-
为什么能够行云流水一般地完成 栈、队列、链表?
这个是非常出乎意料的,以前没有写过这种通过class去构造的写法,实际工作中都是通过数组去简单处理,《JavaScript数据结构与算法》中就是这种写法,并且附带有单元测试,当时好好研读了这本书里的单元测试写法,感觉这么清晰的主要原因99%在于单元测试的编写使人可以更好的抓住每个知识点(感谢TDD)。所以觉得单元测试不重要真的很low。