目录

1. 一个数组旋转 K 步

(1)方法一

(2)方法二

(3)方法三

2.判断一个字符串是否匹配括号

3. 数组创建链表

4.JS 反转单向链表

(1)方法一

(2)方法二

5.两个栈实现一个队列

(1)方法一

(2)方法二

6.链表实现队列

7.二分查找

(1)循环实现

(2)递归

8.寻找一个数组中和为 n 的两个数

(1)常规思路双循环(时间复杂度O(n^2))

(2)嵌套二分查找,时间复杂度为O(logn)

(3)数组是有序的,利用双指针查找,时间复杂度O(n)

9.二叉搜索树的第k最小值

(1)定义二叉搜索树

(2)二叉搜索树三种遍历实现的代码

(3)求二叉搜索树的第k最小值

10. 求斐波那契数列的第 n 值

(1)递归(时间复杂度为 2^n)

(2)循环(时间复杂度为O(n))

11.移动0到数组的末尾

(1)循环实现 (时间复杂度为O(n^2))

(2)循环+双指针实现(时间复杂度O(n))

12.获取字符串中连续最多的字符以次数

(1)双循环实现(因为有“跳步”的存在,所以时间复杂度为O(n))

(2)双指针实现(时间复杂度O(n))

13.用S实现快速排序

14. 获取1-10000之前所有的对称数(回文数)

(1)思路1-使用数组反转、比较

(2)思路2-字符串头尾比较

(3)思路3 生成翻转数

(4)性能分析

15.用 JS 实现数字千分位格式化

(1)用数组实现

(2)字符拆分

(3)数字实现

16.切换字母大小写

(1)正则表达式

(2)通过 SSCII 码判断

总结


        


前言


学习数据结构和算法的记录

1. 一个数组旋转 K 步

(1)方法一

时间复杂度为O(n)

function rotate(arr,k){
    const len = arr.length;
    if(!len || !k) return arr
    //取余数避免重复操作
    const step = Math.abs(k % len);//取绝对值
    for(let i = 0; i< step;i++){
        let n = arr.pop()
        if(n) arr.unshift(n)
    }
    return n
}

(2)方法二

function rotate(arr,k){
    const len = arr.length;
    if(!len || !k) return arr;
    //取余数避免重复操作
    const step = Math.abs(k % len)
    const slice1 = arr.slice(-step)
    const slice2 = arr.slice(0,len - step)
    
    //不会影响原数组
    const slice3 = slice1.concat(slice2)
    return slice3
}

(3)方法三

function rotate (arr,k){
    const len = arr.length
    if(!len || !k) return arr
    
    //取余数避免重复操作
    const step = k % len
    if(step == 0 || isNaN(step)) return arr

    const slice1 = arr.slice(-k)
    const slice2 = arr.slice(0,-k)

    const slice3 = slice1.concat(slice2)
    
    //不会修改原数组
    return slice3
}

2.判断一个字符串是否匹配括号

function isMatch(left,right){
    if(left == '{' && right == '}') return true;
    if(left == '[' && right == ']') return true;
    if(left == '(' && right == ')') return true;
    return false
}

function MatchBracket(str){
    const len = str.length
    if(!len) return true
    const leftSymbol = '{[(';
    const rightSymbol = '}])'
    const stack = []
    for(let i =0 ; i < len ; i++){
        const s = str[i]
           
        //入栈
        if(leftSymbol.includes(s)){
            stack.push(s)
        }else if(rightSymbol.includes(s)){
            const left = stack[ stack.length - 1 ]
            
            //出栈
            if(isMatch(left,s)){
                stack.pop()
            }else{
                return false
            }

        }
    }
    
    return stack.length
    
}

3. 数组创建链表

function creatLinkList(arr){
    const len = arr.length;
    if(!len) return throw new Error('arr is empty')
    let curNode = {
        value : arr[ arr.length -1 ],
    }
    if(len == 1) return curNode
    for(let i = len - 2 ; i >= 0 ; i --){
        curNode = {
            value:arr[i],
            next:curNode
        }
    }
    return curNode
}

4.JS 反转单向链表

(1)方法一

//node 的结构为 {value:1,next:{value:2}}
function reserveLink(node){
    let curNode = null
    let preNode = null
    let nextNode = node;
    while(nextNode){
        if(!preNode){
            delete curNode.next
        }
        if(preNode && curNode){
            curNode.next = preNode
        }
        
        preNode = curNode
        curNode = nextNode
        nextNode = nextNode.next 
        
    }
    curNode.next = preNode
    return curNode
}

(2)方法二

//node 的结构为 {value:1,next:{value:2}}
function reserveLink(node){
    let curNode = {
        value:node.value
    }
    let nextNode = node.next
    while(nextNode){
        curNode =  {
            value : nextNode.value,
            next:curNode
        }
        nextNode = nextNode.next  
    }
    return curNode
}

5.两个栈实现一个队列

(1)方法一

class MyQueue{
    private stack1 = []
    private stack2 = []
    
    add(n){
        this.stack1.push(n)    
    }
    delete(){
        const stack1 = this.stack1
        const stack2 = this.stack2
        while(stack1.length){
            const n = stack1.pop()
            if(n) stack2.push(n)
        }
        const res = stack2.pop()
        while(stack2.length){
            const n = stack2.pop()
            if(n) stack1.push(n)
        }
        return res || null
    }
    get length(){
        return this.stack1.length
    }
}

(2)方法二

class MyQueue {
    private stack1 = []
    private stack2 = []
    
    add(n){
        this.stack1.push(n)
    }
    delete(){
        const stack1 = this.stack1;
        const stack2 = this.stack2
        //继续吐出上次未吐完的
        if(stack2.length) return stack2.pop()
        while(stack1.length){
            const n = stack1.pop()
            if(n) stack2.push(n)
        }
        let res = stack2.pop()
        return res || null
        
    }
}

6.链表实现队列

class MyQueue{
    private head = null
    private tail = null
    private len = 0
    
    add(n){
        const curNode = {
            value:n,
            next:null
        }
        if(!head) this.head = curNode
        const tailNode = this.tail
        if(tailNode) tailNode.next = curNode
        this.tail = curNode
        this.len ++ 
    }
    delete(){
        const headNode = this.head
        if(headNode == null) return null
        if(len == 0) return null
        
        const val = headNode.value
        this.head = headNode.next
        this.len --

        return val
    }
    
    get length(){
        return this.len
    }
}

7.二分查找

(1)循环实现

function binarySearch(arr,target){
    const len = arr.length
    if(!len) return -1
    
    let startIndex = 0;
    let endIndex = len -1;
    while( startIndex <= endIndex){
        const midIndex = Math.floor((startIndex + endIndex) / 2)
        const midValue = arr[midIndex]
        if(target < midValue){
            //目标值较小,则继续在左侧寻找
            endIndex = midIndex - 1
        }else if(target > midValue){
            //目标值较大,则继续在右侧寻找
            starIndex = midIndex + 1
        }else{
            return midIndex
        }
    }
    
    //找不到目标值时
    return -1
}

(2)递归

function binarySearch(arr,target,startIndex,endIndex){
    const len = arr.length;
    if(!len) return -1
    
    if(!startIndex) startIndex = 0
    if(!endIndex) endIndex = len - 1
    
    //相遇则代表没有找到
    if(startIndex > endIndex) return -1
    
    const midIndex = Math.floor((startIndex + endIndex) / 2)
    const midValue = arr[midIndex]
    if(target < midValue){
        //目标值较小,则继续在左侧寻找
        endIndex = midIndex - 1
        binarySearch(arr,target,startIndex,endIndex)
    }else if(target > midValue){
        //目标值较大,则继续在右侧寻找
        startIndex = midIndex + 1
        binarySearch(arr,target,startIndex,endIndex)
    }else{
        return midIndex
    }
}

8.寻找一个数组中和为 n 的两个数

(1)常规思路双循环(时间复杂度O(n^2))

function findTwoNunbers(arr,n){
    const res = []
    const len = arr.length
    if(!len) return res
    for(let i = o; i < len -1; i++){
        const n1 = arr[i]
        let flag = false
        for(let j = i + 1; j < len; j ++){
            const n2 = arr[j]
            if(n1 + n2 === n){
                res.push(n1)
                res.push(n2)
                flag = true
                break
            }
        }
        if(flag) break
    }
    return res
}

(2)嵌套二分查找,时间复杂度为O(logn)

function findTwoNumbers(arr,n){
    const res = []
    const len = arr.length
    if(!len) return res
    for(let i = 0; i < len -1; i++){
        const n1 = arr[i]
        const n2 = n - n1
        if(binarySearch(arr,n2)){
            res.push(n1)
            res.push(n2)
            break
        }
    }
    return res
}

(3)数组是有序的,利用双指针查找,时间复杂度O(n)

function findTwoNumbers(arr,n){
    const len = arr.length
    const res = []
    
    if(!len) return res
    
    let i = 0
    let j = len - 1
    while(i < j){
        const n1 = arr[i]
        const n2 = arr[j]
        const sum = n1 + n2
        if(sum > n){
            //如果 sum 比 n 大,则 j 向前移动
            j--
        }else if(sum < n){
            //如果 sum 比 n 小,则 i 向后移动
            i++
        }else{
            res.push(n1)
            res.push(n2)
            break
        }
    }
    return res
}

9.二叉搜索树的第k最小值

前言:二叉搜索树的遍历顺序:前序、中序、后序

(1)定义二叉搜索树

const node ={
    value:5,
    left:{
        value:3,
        left:{
            value:2,
            left:null,
            right:null
        },
        right:{
            value:4,
            left:null,
            right:null
        }
    },
    right:{
        value:7,
        left:{
            value:6,
            left:null,
            right:null
        },
        right:{
            value:8,
            left:null,
            right:null
        }
    }
}

(2)二叉搜索树三种遍历实现的代码

let arr = []
//二叉搜索树 前序遍历 root -> left -> right
function preOrderTraverse(node){
    if(!node) return null
    console.log(node.value)
    arr.push(node.value) 
    preOrderTraverse(node.left)
    preOrderTraverse(node.right)
}
//二叉搜索树 中序遍历(遍历后数据是有序的)  left -> root -> right
function inOrderTraverse(node){
    if(!node) return null
    inOrderTraverse(node.left)
    console.log(node.value)
    arr.push(node.value)
    inOrderTraverse(node.right)
 }
//二叉搜索树后序遍历 left -> right -> root
function postOrderTraverse(node){
    if(!node) return null
    postOrderTraverse(node.left)
    postOrderTraverse(node.right)
    console.log(node.value)
    arr.push(node.value)
}



//前序遍历后结果
preOrderTraverse(node)
//arr 数据为 [5, 3, 2, 4, 7, 6, 8]

//中序遍历后结果
arr = []
inOrderTraverse(node)
//arr 数据为 [2, 3, 4, 5, 6, 7, 8]

//后序遍历后结果
arr =[]
postOrderTraverse(node)
//arr 数据为 [2, 4, 3, 6, 8, 7, 5]

(3)求二叉搜索树的第k最小值

function getKthValue(node,k){
    //二叉树中序循环
    inOrderTraverse(node)
    return arr[k] || null
}

10. 求斐波那契数列的第 n 值

(1)递归(时间复杂度为 2^n)

function fibonacci(n){
    if(n <= 0) return 0
    if(n === 1) return 1
    
    return fibonacci(n - 1) + fibonacci(n - 2)
}

(2)循环(时间复杂度为O(n))

function fibonacci(n){
    if( n <= 0) return 0
    if( n === 1) return 1
    
    let n1 = 1;// n-1
    let n2 = 0;//n - 2
    let res = 0
    for(let i = 2; i <= n; i++){
       res = n1 + n2

        //记录中间值
        n2 = n1
        n1 = res
    }
    return res
}

11.移动0到数组的末尾

(1)循环实现 (时间复杂度为O(n^2))

function moveZero(arr){
    const len = arr.length
    if(!len || len < 2) return

    let zeroLen = 0
    
    for(let i = 0; i < len - zeroLen; i++){
        if(arr[i] == 0){
            arr.push(0)
            arr.splice(i,1) //这个操作本身就是 O(n)
            zeroLen ++
        }
    }
}

(2)循环+双指针实现(时间复杂度O(n))

//方法一
function moveZero(arr){
    const len = arr.length
    if(!len || len < 2) return 
    
    let i
    let j = - 1;//数组中第一个 0 的下标
    
    for(let i = 0 ; i< len ; i++){
        if(arr[i] == 0 && j < 0){
            //赋值 j 第一个 0 的下标
            j = i
        }
        
        if(arr[i] !== 0 && j >= 0){
            const n = arr[j]
            arr[j] = arr[i]
            arr[i] = n
            j++
        }
    }  
}

//方法二
function moveZero(arr){
    const len = arr.length
    if(!len || len < 2) return
    let j = arr.indexOf(0)
    let i = j + 1
    while(i < len){
        if(arr[i] !== 0){
            arr[j] = arr[i]
            arr[i] = 0
            j++
        }
        i++
    }
}

12.获取字符串中连续最多的字符以次数

(1)双循环实现(因为有“跳步”的存在,所以时间复杂度为O(n))

function findCountinounsChart1(str){
    const len = str.length
    const res = {
        str:'',
        len:0,
    }
    if(!len) return res
    let tempLen = 0
    for(let i = 0; i < len; i++){
        tempLen = 0
        for(let j = 0; j < len; j++){
            if(str[i] == str[j]){
                tempLen++
            }
            if(str[i] !== str[j] || j == len - 1){
                if(tempLen > res.len){
                    res.len = tempLen
                    res.str = str[i]
                }
                if(i < len - 1){
                    i = j - 1 //跳步,j - 1 是因为下一次进入循环 i++
                }
                break
            }
        }
    }
    return res
}

(2)双指针实现(时间复杂度O(n))

function findCountinounsChart2(str){
    const len = str.length
    const res = {
        str:'',
        len:0
    }
    let tempLen = 0
    let i = 0
    let j = 0
    for(; i < len; i++){
        if(str[i] == str[j]){
            tempLen++
        }
        if(str[i] !== str[j] || i == len - 1){
            if(tempLen > res.len){
                res.len = tempLen
                res.str = str[j]
            }
            tempLen = 0
            if(i < len - 1){
                j = i
                i-- //细节为了下次进入循环i 和 j 保持一致
            }
        }
    }
    return res
}

13.用S实现快速排序

//方法一与方法二时间复杂度统一为O(n*logn)
//方法一,用 splice 实现
function quickSort1(arr){
    const len = arr.length
    if(!len) return arr
    
    const midIndex = Math.floor(len / 2)
    const midValue = arr.splice(midIndex,1)
    
    const left = [] //小于中间值的放在left
    const right = [] //大于中间值的放在right


    //splice 会更改原数组,所以这里需要使用 arr.length
    for(let i = 0; i < arr.length; i++){
        const n = arr[i]
        if(n < midValue){
            left.push(n)
        }else{
            right.push(n)
        }

   }

    return quickSort1(left).concat([midValue],quickSort1(right))
}

//方法二  用 slice 实现
function quickSort2(arr){
    const len = arr.length
    if(!len) return len
    
    const midIndex = Math.floor(len / 2)
    const midValue = arr.slice(midIndex,midIndex + 1)

    const left = []
    const right = []
    
    for(let i = 0; i < len; i++){
        if(i !== midIndex){
            const n = arr[i]
            if(n < midValue){
                left.push(n)
            }else{
                right.push(n)
            }
        }
        
    }
    return quickSort2(left).concat([midValue],quickSort2(right))
}

14. 获取1-10000之前所有的对称数(回文数)

(1)思路1-使用数组反转、比较

function findPalindromeNumbers1(max){
    const res = []
    if(max <= 0) return res
    for(let i = 1; i <= max; i++){
        const s = i.toString()
        if(s == s.split('').reverse().join('')){
            res.push(i)
        }
    }
    return res
}

(2)思路2-字符串头尾比较

function findPalindromeNumbers1(max){
    const res = []
    if(max <= 0) return res
    for(let i = 1; i <= max; i++){
        const s = i.toString()
        const len = s.length
        let startIndex = 0
        let endIndex = len - 1
        let flag = true

        while(startIndex < endIndex){
            if(s[startIndex] !== s[endIndex]){
               flag = false
                break 
           }else{
                startIndex++
                endIndex--
            }
        }
        flag && res.push(i)

    }
    return res
}

(3)思路3 生成翻转数

function findPalindromeNumbers1(max){
    const res = []
    if(max <= 0) return res
    for(let i = 1; i <= max; i++){
       let n = i
       let rev = 0
       while(n > 0){
            rev = rev * 10 + n % 10
            n = Math.floor(n/10)
       } 
        rev == i && res.push(i)
       
    }
    return res
}

(4)性能分析

  1. 思路1 - 看似O(n),但数组转换,操作都需要时间,所以慢
  2. 思路2 VS 思路3 ,操作数字更快(电脑原型就是计算器)

15.用 JS 实现数字千分位格式化

(1)用数组实现

function format1(n:number):string{
    n = Math.floor(n)
    const s = n.toString()
    const arr = s.split('').reverse()
    return arr.reduce((prev,val,index) =>{
        if(index%3 == 0){
            if(prev){
                return val +','+prev
            }else{
                return val
            }
        }else{
            return val + prev
        }
    },'')
}

(2)字符拆分

function format2(n:number):string{
    n = Math.floor(n)
    const s = n.toString()
    const len = s.length
    let res = ''
    for(let i = len - 1; i >= 0; i++){
        const j = len - i
        if(j % 3 == 0){
            if(i == 0){
                res = s[i] + res
            }else{
                res = ',' + s[i] + res
            }
        }else{
            res = s[i] + res
        }
    }
    return res
}

(3)数字实现

function format3(n:number):string{
    const num = Math.floor(n)
    let format = ''
    while(num > 0){
        let res:number|string = num % 1000
        num = Math.floor(num/1000)
        if(num > 0){
            if(res == 0){
                res = ',000'
            }else if(res < 10){
                res = ',00'+res
            }else if(res < 100){
                res = ',0'+res
            }else if(res < 1000){
                res = ','+res
            }
        }else{
            //小于等于0则说明是最后三位,则无需加千位分隔符
            res = res
        }
        format = res + format
    }
    return format
    
}

16.切换字母大小写

(1)正则表达式

function switchLetterCase1(str:string):string{
    let res = ''
    if(!str.length) return res
    const reg1 = /[a-z]/ //小写字母正则
    const reg2 = /[A-Z]/ //大写字母正则

    for(let i = 0; i < str.length; i++){
        const n = str[i]
        if(reg1.test(n)){
            res = res + n.toLocaleUpperCase()
        }else if(reg2.test(n)){
            res = res + n.toLocaleLowerCase()
        }else{
            res = res + n
        }
    }
    return res
}

(2)通过 SSCII 码判断

function switchLetterCase1(str:string):string{
    let res = ''
    if(!str.length) return res
    for(let i = 0; i < str.length; i++){
        const n = str[i]
        const code = n.charCodeAt(0)
        if(code >= 65 && code <= 90){
            //大写转小写
            res = res + n.toLocaleLowerCase() 
        }else if(code >= 97 && code <= 122){
            //小写转大写
            res = res + n.toLocaleUpperCase()
        }else{
            res = res + c
        }
    }
    return res
}