面试复习篇1:数据结构与算法篇(上)

648 阅读6分钟

面试复习篇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时,检测到weakMapkey对应的对象如果被回收了,会自动删除映射

集合

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)

shiftunshift本身都是高时间复杂度的操作,应该是要用别的方式替换掉的

算法

|- 分而治之
|- 动态规划
|- 贪心
|- 回溯

排序算法

|- 冒泡
|- 快速
|- 插入
|- 归并
|- 选择

搜索算法

|- 顺序搜索
|- 二分搜索

总结

先纯印象,归纳出知识结构,然后按照结构默写,没有复习过,不借助任何外力,单纯凭脑海中的知识点复现,写时能明显感受到沉睡的知识点逐渐复苏变得清晰,也可以明确的知道自己记得牢靠的点有哪些,忘了的,模糊的点又有哪些,经过总结后面可以更直观的查缺补漏

  • 完全遗忘: 排序算法、搜索算法、双栈实现一个队列、二叉树的规律,后面再补充实际应用场景

  • 卡壳很久才写出来: 生成二叉树

  • 有印象但感觉模糊的:iterator、generator;weakMap的弱引用和印象里有些差别

  • 行云流水完成的: 栈、队列、链表(后面应该可以不用关心的)

分析

  • 完全遗忘是因为知识点忘了,无法回想起来,补充完知识点应该能做出来一部分,比如排序,只记得名字是什么,忘了每种排序的知识点分别是什么

  • 为什么能够行云流水一般地完成 栈、队列、链表?

这个是非常出乎意料的,以前没有写过这种通过class去构造的写法,实际工作中都是通过数组去简单处理,《JavaScript数据结构与算法》中就是这种写法,并且附带有单元测试,当时好好研读了这本书里的单元测试写法,感觉这么清晰的主要原因99%在于单元测试的编写使人可以更好的抓住每个知识点(感谢TDD)。所以觉得单元测试不重要真的很low。