递归与栈专题一

226 阅读5分钟

这是我参与2022首次更文挑战的第8天,活动详情查看:2022首次更文挑战 | 创作学习持续成长,夺宝闯关赢大奖 - 掘金 (juejin.cn)

题目链接

  1. 化栈为队 leetcode-cn.com/problems/im…
  2. 棒球比赛 leetcode-cn.com/problems/ba…
  3. 比较含退格的字符串 leetcode-cn.com/problems/ba…
  4. 验证栈序列 leetcode-cn.com/problems/va…
  5. 有效的括号 leetcode-cn.com/problems/va…

题解及分析

化栈为队

实现一个MyQueue类,该类用两个栈来实现一个队列。 示例:
MyQueue queue = new MyQueue();
queue.push(1);
queue.push(2);
queue.peek(); // 返回 1
queue.pop(); // 返回 1
queue.empty(); // 返回 false
说明:
你只能使用标准的栈操作 -- 也就是只有push to top,peek/pop from top,size和is empty操作是合法的。
你所使用的语言也许不支持栈。你可以使用list或者deque(双端队列)来模拟一个栈,只要是标准的栈操作即可。
假设所有操作都是有效的(例如,一个空的队列不会调用pop或者peek操作)。

题目需要模拟实现一个队列,限制是只能使用标准的栈操作

  • 维护两个栈,一个负责入队,一个负责出队
  • 入队没有其他操作
  • 出队需要将入队栈翻转,去掉最后一个元素(也就是入队栈的第一个元素),然后将出队栈翻转回去存回入队栈
  • peek原理如出队栈
  • empty判断两个栈长度是否均为空
var MyQueue = function() {
    this.stackIn = []
    this.stackOut = []
};

MyQueue.prototype.push = function(x) {
    this.stackIn.push(x)
};

MyQueue.prototype.pop = function() {
    while(this.stackIn.length > 1){
        this.stackOut.push(this.stackIn.pop())
    }
    let ans =  this.stackIn.pop()
    while(this.stackOut.length){
        this.stackIn.push(this.stackOut.pop())
    }
    return ans
};

MyQueue.prototype.peek = function() {
    while(this.stackIn.length) {
        this.stackOut.push(this.stackIn.pop())
    }
    let ans = this.stackOut[this.stackOut.length - 1]
    while(this.stackOut.length) {
        this.stackIn.push(this.stackOut.pop())
    }
    return ans
};

MyQueue.prototype.empty = function() {
    return !this.stackIn.length && !this.stackOut.length;
};

棒球比赛

你现在是一场采用特殊赛制棒球比赛的记录员。这场比赛由若干回合组成,过去几回合的得分可能会影响以后几回合的得分。
比赛开始时,记录是空白的。你会得到一个记录操作的字符串列表ops,其中ops[i]是你需要记录的第i项操作,ops遵循下述规则:
整数 x - 表示本回合新获得分数x
"+" - 表示本回合新获得的得分是前两次得分的总和。题目数据保证记录此操作时前面总是存在两个有效的分数。
"D" - 表示本回合新获得的得分是前一次得分的两倍。题目数据保证记录此操作时前面总是存在一个有效的分数。
"C" - 表示前一次得分无效,将其从记录中移除。题目数据保证记录此操作时前面总是存在一个有效的分数。
请你返回记录中所有得分的总和。

思路:

  • 维护一个栈用来记录数字
  • 遇到不同的符号时,对栈顶元素进行操作
  • 使用一个变量来记录每轮循环之后栈顶元素的和,在C的时候跳过
var calPoints = function (ops) {
    let scoreStack = []
    let sum = 0
    for (let i = 0; i < ops.length; i++) {
        if (+ops[i]){
            scoreStack.push(+ops[i]
        } else if (ops[i] === '+') {
            scoreStack.push(scoreStack[scoreStack.length - 1] + scoreStack[scoreStack.length - 2])
        } else if (ops[i] === 'C') {
            sum -= scoreStack.pop()
            continue;
        } else if (ops[i] === 'D') {
            scoreStack.push(scoreStack[scoreStack.length - 1] * 2)
        }
        sum += scoreStack[scoreStack.length - 1]
    }
    return sum
};

使用reduce:

var calPoints = function (ops) {
    let scoreStack = [];
    for (let i = 0; i < ops.length; i++) {
        if (+ops[i]){
            scoreStack.push(+ops[i]);
        } else if (ops[i] === '+') {
            scoreStack.push(scoreStack[scoreStack.length - 1] + scoreStack[scoreStack.length - 2])
        } else if (ops[i] === 'C') {
            scoreStack.pop();
        } else if (ops[i] === 'D') {
            scoreStack.push(scoreStack[scoreStack.length - 1] * 2)
        }
    }
    return scoreStack.reduce((a, b) => a + b)
};

比较含退格的字符串

给定 s 和 t 两个字符串,当它们分别被输入到空白的文本编辑器后,如果两者相等,返回 true 。# 代表退格字符。
注意:如果对空文本输入退格字符,文本继续为空。
提示:
1 <= s.length, t.length <= 200
s和t只含有小写字母以及字符'#'
进阶:
你可以用O(n)的时间复杂度和O(1)的空间复杂度解决该问题吗?

方案一:栈

var backspaceCompare = function(s, t) {
    return processed(s) === processed(t)
};

var processed = (str) => {
    let stack = []
    for(ch of str) {
        if(ch === '#') {
            stack.pop()
        } else {
            stack.push(ch)
        }
    }
    return stack.join('')
}

方法二:

  • 用两个变量记录两个字符中的#
  • 同时遍历两个字符串
    • 如果是#则变量加一
    • 如果不是#,记录当前字符串中#的数量的变量-1,如果变量为0则跳过这次循环
    • 一直到取得下一个有效字符
  • 对比这两个中下标所在的字符是否相同,不同则直接返回false
var backspaceCompare = function(s, t) {
    let skipS = 0
    let skipT = 0
    let i = s.length - 1
    let j = t.length - 1;

    while(i >= 0 || j >= 0) {
        while(i >= 0){
            if(s[i] === '#'){
                skipS++
                i--
            }else if(skipS > 0){
                skipS--
                i--
            }else break
        }

        while(j >= 0) {
            if(t[j] === '#') {
                skipT++
                j--
            } else if(skipT > 0) {
                skipT--
                j--
            } else {
                break
            }
        }

        if(s[i] !== t[j]) return false
        i--
        j--
    }

    return true
}

验证栈序列

给定pushed和popped两个序列,每个序列中的值都不重复,只有当它们可能是在最初空栈上进行的推入push和弹出pop操作序列的结果时,返回true;否则,返回false。

这道题实际上是考察栈的出栈顺序

  • 维护一个栈存入pushed
  • 需要维护一个指针,这个指针每次只走一个单位
  • 在出栈前对某个需要出栈的元素和popped中指针指向的元素进行比对
var validateStackSequences = function(pushed, popped) {
    const stack = []
    let index = 0
    const length = pushed.length
    for(let i = 0; i < length; i++) {
        stack.push(pushed[i])
        // 只对相同的字符串进行出栈,index正常情况下应该刚好和i相等
        while(popped[index] !== undefined && popped[index] === stack[stack.length - 1]) {
            stack.pop()
            index++
        }
    }
    return !stack.length
}

有效的括号

给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。

思路:

  • 维护一个栈
  • 遍历字符串,存入(,[,{
  • 如果拿到),],},则比对栈中是否存在对应的(,[,{
var isValid = function(s) {
    const n = s.length
    if(n % 2 === 1) return false
    // 感谢leetcode,map是真的用的少
    pairs = new Map([
        [')', '('],
        [']', '['],
        ['}', '{']
    ])
    const stack = []
    for(let ch of s) {
        if(pairs.has(ch)) {
            if(stack[stack.length - 1] !== pairs.get(ch) || !stack.length) {
                return false
            }
            stack.pop()
        } else {
            stack.push(ch)
        }        
    }
    return !stack.length
};

题目总结

这类题目的重点在于首先搞清楚顺序,队列fifo(先进先出),栈filo(后进先出),这几道题目实际上难度并不大,但搞清楚题意和知悉想要考察的点比较重要~