本文github地址:JavaScript_Everything 大前端知识体系与面试宝典,从前端到后端,全栈工程师,成为六边形战士
栈和数组的区别
二者没有联系。栈是逻辑结构,是一个理论模型。但数组是一个计算机中的物理实现。数组可以用来实现栈。
判断一个字符串是否括号匹配(栈)
思路:
- 遇到左括号
({[
进栈,遇到右括号]})
出栈 - 判断两个字符是否相等,不相等直接返回为false
- 判断栈是否为空
const isEqual = (a:string,b:string):boolean =>
(a === '[' && b === ']') || (a === '{' && b === '}') || (a === '(' && b === ')') ? true : false
function isValid(str:string):boolean {
const len = str.length
if(len === 0) return true
const stack:string[] = []
const leftSymbols:string = '({['
const rightSymbols:string = '}])'
for (let index = 0; index < len; index++) {
let s:string = str[index];
if(leftSymbols.includes(s)){
stack.push(s)
}
if(rightSymbols.includes(s)){
let top:string = stack.pop() as string
if(!isEqual(top, s)) return false
}
}
return stack.length === 0
}
let s = '({[]})'
console.info(isValid(s)) // false
性能分析:
- 时间复杂度:
O(n)
- 空间复杂度:
O(n)
用两个栈实现一个队列(栈、队列)
class Myqueue {
private stack1:number[] = []
private stack2:number[] = []
add(n:number):void{
this.stack1.push(n)
}
delete():number | null {
const stack1 = this.stack1
const stack2 = this.stack2
// 将stack1中的元素腾挪到stack2
while(stack1.length){
const n = stack1.pop() as number
if(n !== null) stack2.push(n)
}
// 栈顶出栈(队头出队)
let res = stack2.pop()
// 将stack2中的元素腾挪到stack1
while(stack2.length){
const n = stack2.pop() as number
if(n !== null) stack1.push(n)
}
return res || null
}
get length():number{
return this.stack1.length
}
}
const q = new Myqueue()
q.add(1)
q.add(2)
console.log(q.delete())
性能分析:
- 时间复杂度:add是
O(1)
,delete是O(n)
- 空间复杂度:
O(n)
反转单向链表(链表)
构建节点类
class SingleNode<T>{
value:T
next?: SingleNode<T> | null = null
constructor(value:T){
this.value=value
}
}
构建链表类
class SingleLink<T>{
private head:SingleNode<T> | null = null
append(val:T){
let newNode = new SingleNode(val)
if(!this.head){
this.head = newNode
}else{
let current = this.head
while(current.next){
current = current.next
}
current.next = newNode
}
}
appendArray(arr:T[]){
arr.forEach(val => this.append(val))
}
showItems(){
let current = this.head
if(!current) return
let res = ''
while(current){
res += `${current.value}->`
current = current?.next || null
}
return res
}
getHead():SingleNode<T> | null{
return this.head
}
}
测试链表类,并将结果打印出来
const link:SingleLink<number> = new SingleLink()
link.appendArray([1,2,3,4,5])
console.log(link.showItems())
结果为:1->2->3->4->5->
然后实现链表反转的函数:
function reverseLink(link:SingleLink<number>):SingleNode<number> | null | undefined{
let head:SingleNode<number> | null = link.getHead()
if(!head) return null
if(!head.next) return head
let pre:SingleNode<number> | null | undefined = null
let next:SingleNode<number> | null | undefined = null
while (head !== null) {
next = head.next;
head.next = pre;
pre = head;
head = next!;
}
return pre
}
查看得到的新头结点,并打印新的链表
let newHead:SingleNode<number> | null | undefined = reverseLink(link)
let res = ''
while(newHead){
res += `${newHead.value}->`
newHead = newHead.next
}
console.log(res)
结果为:5->4->3->2->1->
二分查找(二分)
性能分析:
- 时间复杂度:
O(logn)
先来写一个生成随机数的函数
// 生成指定长度的随机数数组
function random(len:number, max:number = 10000):number[]{
const res:number[] = []
for (let index = 0; index < len; index++) {
let num = Math.floor(Math.random() * max)
res.push(num)
}
return res
}
const randomArray = random(100)
二分查找函数:
function searchBinaryByWhile(arr:number[], target:number):number{
const len = arr.length
if(len === 0) return -1
let startIndex = 0
let endIndex = len - 1
while(startIndex <= endIndex){
const midIndex = Math.floor((startIndex + endIndex) / 2)
const midVal = arr[midIndex]
if(target < midVal){
endIndex = midIndex - 1
}else if(target > midVal) {
startIndex = midIndex + 1
}else{
return midIndex
}
}
return -1
}
要求数组是已经排序的,因此对随机数组进行一些处理
const randomArray = random(100, 100)
const sortedArray = randomArray.sort((a,b)=>a-b)
console.log(sortedArray)
const res = searchBinaryByWhile(sortedArray,10)
console.log(res)
用递归实现二分查找:
function searchBinaryByRecurve(arr:number[], target:number, startIndex:number = 0, endIndex?:number):number{
const len = arr.length
if(len === 0) return -1
if(!endIndex) endIndex = len -1
if(startIndex > endIndex) return -1
const midIndex = Math.floor((startIndex + endIndex) / 2)
const midVal = arr[midIndex]
// 分治递归
if(target < midVal){
return searchBinaryByRecurve(arr, target, startIndex, midIndex - 1)
}else if(target > midVal){
return searchBinaryByRecurve(arr, target, midIndex + 1, endIndex)
}else{
return midIndex
}
}
循环和递归进行比较:
const randomArray = random(100000, 1000000)
const sortedArray = randomArray.sort((a,b)=>a-b)
console.time('searchBinaryByWhile')
const res1 = searchBinaryByWhile(sortedArray,1000)
console.timeEnd('searchBinaryByWhile')
console.time('searchBinaryByRecurve')
const res2 = searchBinaryByRecurve(sortedArray,1000)
console.timeEnd('searchBinaryByRecurve')
注意📢:凡有序,必二分
找出一个排序数组中和为 n 的两个数(二分+双指针)
依然使用上面实现的生成随机数组的函数:
// 生成指定长度的随机数数组
function random(len:number, max:number = 10000):number[]{
const res:number[] = []
for (let index = 0; index < len; index++) {
let num = Math.floor(Math.random() * max)
res.push(num)
}
return res
}
const randomArray = random(100,100)
const sortedArray = randomArray.sort((a,b)=>a-b)
使用for嵌套循环暴力查找:
function findNumbers(arr:number[], target:number):number[]{
const res: number[] = []
const len = arr.length
if(len === 0) return res
for (let i = 0; i < len; i++) {
const n1 = arr[i];
let flag = false
for (let j = i + 1; j < len; j++) {
const n2 = arr[j];
if(n1 + n2 === target){
res.push(n1)
res.push(n2)
flag = true
break
}
}
if(flag) break
}
return res
}
测试:
const res = findNumbers(sortedArray, 100)
console.log(res)
时间复杂度分析:O(n^2)
我们可以利用递增的特性,想到二分的思想:选取第一个数和最后一个数相加,如果和大于目标数,则第一个数向右移动,反之,最后一个数向左移动。即使用双指针
的思想,将时间复杂度降低到O(n)
function findNumbers(arr:number[], target:number):number[]{
const res: number[] = []
const len = arr.length
if(len === 0) return res
let left = 0, right = len - 1
// 双指针移动
while(left <= right){
let sum = arr[left] + arr[right]
if(sum > target){
right -= 1
}else if(sum < target){
left += 1
}else {
res.push(...[arr[left] , arr[right]])
return res
}
}
return res
}
时间复杂度:O(n)
二叉树及二叉树遍历(二叉树+分治+递归)
前序遍历:root->left-right
中序遍历:left->root->right
后序遍历:left->right->root
构造树节点:
class TreeNode {
public data:string | number
public leftChild:TreeNode | null
public rightChild:TreeNode | null
constructor(data:string | number){
this.data = data
this.leftChild = null
this.rightChild = null
}
}
构建树(使用数组):
const createTree = (arr:string[] | number[]):TreeNode | null | undefined => {
// 创建二叉树
let tree = new TreeNode(arr[0])
let Nodes = [tree]
let i = 1
for (let node of Nodes){
Nodes.push(node.leftChild = new TreeNode (arr[i]))
i += 1
if (i == arr.length) return tree
Nodes.push(node.rightChild = new TreeNode(arr[i]))
i += 1
if (i == arr.length) return tree
}
}
测试:
let datas = ['A','B','C','D','E','F','G','H']
let tree = createTree(datas)
console.info(tree)
前序遍历
function prevOrderTree(node:TreeNode){
if(node === null) return
console.log(node.val)
prevOrderTree(node.leftChild!)
prevOrderTree(node.rightChild!)
}
中序遍历
function midOrderTree(node:TreeNode){
if(node === null) return
midOrderTree(node.leftChild!)
console.log(node.val)
midOrderTree(node.rightChild!)
}
后续遍历
function afterOrderTree(node:TreeNode){
if(node === null) return
afterOrderTree(node.leftChild!)
afterOrderTree(node.rightChild!)
console.log(node.val)
}
求二叉搜索树的第K小值
二叉搜索树的特点:
left(包括其后代) val <= root.val
right(包括其后代) val >= root.val
可使用二分查找快速检索
分析:二叉搜索树的中序遍历是按照从小到大
顺序,可以直接按照中序遍历的结果获取二叉搜索树的第K小值
利用刚才的二叉树函数快速构建二叉搜索树:
let datas = [5,3,7,2,4,6,8]
let tree:TreeNode = createTree(datas) as TreeNode
function midOrderTree(node:TreeNode){
if(node === null) return
midOrderTree(node.leftChild!)
// @ts-ignore
res.push(node.val)
midOrderTree(node.rightChild!)
}
const res:number[] | string[] = []
function findKValue(tree:TreeNode,k:number):number{
midOrderTree(tree)
return res[k-1] as number
}
console.log(findKValue(tree,3)) // 4
本文github地址:JavaScript_Everything 大前端知识体系与面试宝典,从前端到后端,全栈工程师,成为六边形战士