前端算法入门之路(三)(递归与栈)

162 阅读3分钟

船长表情包镇楼

图片名称

栈是⼀种“先进后出”(FILO, First In Last Out)的数据结构

栈适合解决什么问题?

  • 处理具有完全包含关系的问题(匹配问题、函数调用关系、树与递归等)

LeetCode肝题

  1. 面试题 03.04. 化栈为队
// 使用两个栈实现一个队列的功能
var MyQueue = function() {
    this.s1 = []
    this.s2 = []
};
MyQueue.prototype.push = function(x) {
    this.s2.push(x)
};
MyQueue.prototype.pop = function() {
    if (this.s1.length) {
        return this.s1.pop()
    }
    let length = this.s2.length
    for(let i = 0; i < length; i++) {
        this.s1.push(this.s2.pop())
    }
    return this.s1.pop()
};
MyQueue.prototype.peek = function() {
    if (this.s1.length) {
        return this.s1[this.s1.length - 1]
    }
    let length = this.s2.length
    for(let i = 0; i < length; i++) {
        this.s1.push(this.s2.pop())
    }
    return this.s1[this.s1.length - 1]
};
MyQueue.prototype.empty = function() {
    return this.s1.length === 0 && this.s2.length === 0
};
    1. 棒球比赛
// 按照流程处理栈顶元素,最后遍历求和
var calPoints = function(ops) {
    var arr = []
    for(let i = 0; i < ops.length; i++) {
        switch(ops[i]){
            case '+':
            const ret = arr.pop(), sum = arr[arr.length - 1] + ret
            arr.push(ret)
            arr.push(sum)
            break
            case 'D':
            arr.push(arr[arr.length - 1] * 2)
            break
            case 'C':
            arr.pop()
            break
            default:
            arr.push(parseInt(ops[i]))
        }
    }
    let sum = 0
    for(let i = 0; i < arr.length; i++) {
        sum += arr[i]
    }
    return sum
};
    1. 比较含退格的字符串
// 定义一个处理字符串的方法,判断字符类型如果是#栈弹出,否则插入
var transform = function(str, arr) {
    for(let i = 0; i < str.length; i++) {
        if (str[i] == '#' && arr.length > 0) arr.pop()
        else if (str[i] != '#') arr.push(str[i])
    }
}
var backspaceCompare = function(S, T) {
    let arr1 = [], arr2 = []
    transform(S, arr1)
    transform(T, arr2)
    if (arr1.length != arr2.length) return false
    for(let i = 0; i < arr2.length; i++) {
        if (arr2[i] != arr1[i]) return false
    }
    return true
};
    1. 验证栈序列
// 定位到pushed的元素与popped第一个元素相等的位置,将匹配的值弹出,如果最终为空,则能验证栈序列
var validateStackSequences = function(pushed, popped) {
    let arr = []
    for(let i = 0, j = 0; i < popped.length; i++) {
        arr.push(pushed[i])
        while(arr.length && arr[arr.length - 1] == popped[j]) {
            arr.pop()
            j++
        }
    }
    return arr.length == 0
};
    1. 有效的括号
// 定义一个对象存放对应关系,将字符依次存入栈内,如果匹配上,则弹出栈顶元素,最终如果栈为空则是有效的括号
var isValid = function(s) {
const mapObj = {
        '(': ')',
        '[': ']',
        '{': '}'
    }
    const arr = []
    for (let i = 0; i< s.length; i++) {
        if (s[i] in mapObj) {
            arr.push(s[i])
        } else {
            if (s[i] != mapObj[arr.pop()]) {
                arr.push(s[i])
            }
        }
    }
    return !arr.length
};
    1. 删除最外层的括号
// 通过对括号计数可以去掉最外层的括号
var removeOuterParentheses = function(S) {
    let str = '', num = 0
    for (let i = 0; i < S.length; i++) {
        if (S[i] == '(' && num++ > 0) str += S[i]
        if (S[i] == ')'&& --num > 0) str += S[i]
    }
    return str
};
    1. 移除无效的括号
// 定义arr存储左括号下标,遇到匹配的右括号则弹出,无法匹配的右括号下标存到delArr里
// 连接两个数组,将s里把下标对应的值删除
var minRemoveToMakeValid = function(s) {
    let arr = [], delArr = []
    for(let i = 0; i<s.length; i++) {
        if (s[i] == '(') {
            arr.push(i)
        } else if (s[i] == ')') {
            if (arr.length){
                arr.pop()
            } else {
                delArr.push(i)
            }
        }
    }
    const filter = arr.concat(delArr), filterArr = [...s]
    for(let i = 0; i<filter.length; i++) {
        filterArr[filter[i]] = ''
    }
    return filterArr.join('')
};
    1. 二叉树的后序遍历
// 定义一个执行栈arr,定义一个状态栈process,根据不同的状态执行不同的操作
var postorderTraversal = function(root) {
    if (!root) return []
    let arr = [root], process = [0], ret = []
    while(arr.length > 0) {
        switch(process.pop()) {
            case 0:
            process.push(1)
            if (arr[arr.length-1].left) {
                arr.push(arr[arr.length-1].left)
                process.push(0)
            }
            break
            case 1:
            process.push(2)
            if (arr[arr.length-1].right) {
                arr.push(arr[arr.length-1].right)
                process.push(0)
            }
            break
            case 2:
            ret.push(arr[arr.length-1].val)
            arr.pop()
            break
        }
    }
    return ret
};
    1. 验证二叉树的前序序列化
// 在遍历preorder的时候通过判断如果是数字加##则合并成一个#,最终如果只剩一个#就可以验证该序列
var isValidSerialization = function(preorder) {
    if (preorder == '#') return true
    let arr = [], tree = preorder.split(',')
    for(let i = 0; i < tree.length; i++) {
        arr.push(tree[i])
        if (arr[0] == '#' && arr.length == 1) return false
        while(arr.length > 2 && arr[arr.length - 1] == '#' && arr[arr.length - 2] == '#' && arr[arr.length - 3] != '#') {
            arr.pop()
            arr.pop()
            arr.pop()
            arr.push('#')
        }
    }
    return arr[0] == '#' && arr.length == 1
};
    1. 基本计算器 II
// 设定一个优先级,+-为1,*/为2,小技巧是增加一个标识符在最后优先级最低表示结束
// 设定一个数字栈和操作符栈,在遍历字符串表达式s的时候如果当前操作符的优先级小于操作符栈的栈顶元素,则执行计算操作
// 计算操作是数字栈的两个栈顶元素和操作符栈的一个栈顶元素计算
var level = function(val) {
    switch (val) {
        case '+':
        case '-': return 1
        case '*':
        case '/': return 2
        case '@': return -1
    }
    return 0
}
var calc = function(a, b, ops) {
    switch (ops) {
        case '+': return a + b
        case '-': return a - b
        case '*': return a * b
        case '/': return parseInt(a / b)
    }
}
var calculate = function(s) {
    let num = [], ops = []
    s += '@'
    for(let i = 0, n = 0; i < s.length; i++) {
        if (s[i] == ' ') continue
        if (level(s[i]) == 0) {
            n = n * 10 + parseInt(s[i])
            continue
        }
        num.push(n)
        n = 0
        while(ops.length > 0 && level(s[i]) <= level(ops[ops.length - 1])) {
            let b = num.pop()
            let a = num.pop()
            num.push(calc(a,b,ops.pop()))
        }
        ops.push(s[i])
    }
    console.log(num)
    return num[0]
};
    1. 函数的独占时间
// 将id作为结果集的下标,根据状态判断
// 如果状态是start,id对应的值等于当前时间 - pre,pre等于上一次end时候的时间 + 1
// 如果状态是edn,id对应的值等于当前时间 - pre + 1,pre等于上一次start时候的时间 + 1
var exclusiveTime = function(n, logs) {
    let ans = Array(n).fill(0), ids = []
    for (let i = 0, pre = 0; i < logs.length; i++) {
        let arr = logs[i].split(':')
        if (arr[1] == 'start') {
            if (ids.length > 0) {
                ans[ids[ids.length - 1]] += parseInt(arr[2]) - pre
            }
            ids.push(parseInt(arr[0]))
            pre = parseInt(arr[2])
        } else {
            ans[arr[0]] += parseInt(arr[2]) - pre +1
            pre = parseInt(arr[2]) + 1
            ids.pop()
        }
    }
    return ans
};
    1. 表现良好的最长时间段
// 将工作时间表转化,大于8的记1,小于8的记-1,然后求出前缀和数组preSum
// 「表现良好时间段」的最大长度实际上也是preSum两个位置的值差大于0并且位置的下标差最大
// 定义一个stack存储preSum所有值小于0的下标,减去一个负数更容易大于0
// 遍历preSum,找到所有大于stack里对应值的下标,寻找下标差值的最大值
var longestWPI = function(hours) {
    let preSum = new Array(hours.length + 1).fill(0), stack = [0], max = 0
    for(let i = 0; i < hours.length; i++) {
        if (hours[i] > 8) preSum[i+1] = preSum[i] + 1
        else preSum[i+1] = preSum[i] - 1
    }
    for(let i = 1; i < preSum.length; i++) {
        if (preSum[i] < preSum[stack[stack.length-1]]) stack.push(i)
    }
    for(let i = preSum.length - 1; i > max; i--) {
        while(stack.length && preSum[stack[stack.length-1]] < preSum[i]) {
            max = Math.max(max, i - stack.pop())
        }
    }
    return max
};