【路飞】-算法练习——栈的经典题、二叉树逆序遍历

220 阅读6分钟

有效的括号

题目】: 给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。

有效字符串需满足:

左括号必须用相同类型的右括号闭合。 左括号必须以正确的顺序闭合。

【示例1:】

输入: s = "()"
输出: true

【示例 2】:

输入: s = "()[]{}"
输出: true

【示例 3】:

输入: s = "(]"
输出: false

【示例 4】:

输入: s = "([)]"
输出: false

【示例 5】:

输入: s = "{[]}"
输出: true

【思考:】一对括号可以理解为栈的入栈和出栈,那么如果有一个有效字符串,表现为最终的栈就是一个空栈,用数组来实现,循环字符串,添加每个字符到数组中,如果出现相邻两个元素是一对括号,就去除这一对元素,剩下的数组中如果还有元素,则表示不是一个有效的字符串

【代码:】

/**
 * @param {string} s
 * @return {boolean}
 */
var isValid = function(s) {
    let sMap = {
        '(': 1,
        ')': -1,
        '[': 2,
        ']': -2,
        '{': 3,
        '}': -3
    }
    let ret = []
    for(let i = 0; i<s.length;i++){
        let length = ret.length
        if(length && sMap[ret[length-1]] > 0 && sMap[ret[length-1]]+sMap[s[i]]===0 ){
            ret.pop()
        }else{
            ret.push(s[i])        
        }
    }
    if(ret.length){
        return false
    }
    return true

};

删除最外层的括号

题目】: 有效括号字符串为空 ""、"(" + A + ")" 或 A + B ,其中 A 和 B 都是有效的括号字符串,+ 代表字符串的连接。

例如,"","()","(())()" 和 "(()(()))" 都是有效的括号字符串。

如果有效字符串 s 非空,且不存在将其拆分为 s = A + B 的方法,我们称其为原语(primitive),其中 A 和 B 都是非空有效括号字符串。

给出一个非空有效字符串 s,考虑将其进行原语化分解,使得:s = P_1 + P_2 + ... + P_k,其中 P_i 是有效括号字符串原语。

对 s 进行原语化分解,删除分解中每个原语字符串的最外层括号,返回 s

【示例1:】

输入:s = "(()())(())"
输出:"()()()"
解释:
输入字符串为 "(()())(())",原语化分解得到 "(()())" + "(())",
删除每个部分中的最外层括号后得到 "()()" + "()" = "()()()"。

【示例 2】:

输入:s = "(()())(())(()(()))"
输出:"()()()()(())"
解释:
输入字符串为 "(()())(())(()(()))",原语化分解得到 "(()())" + "(())" + "(()(()))",
删除每个部分中的最外层括号后得到 "()()" + "()" + "()(())" = "()()()()(())"。

【示例 3】:

输入:s = "()()"
输出:""
解释:
输入字符串为 "()()",原语化分解得到 "()" + "()",
删除每个部分中的最外层括号后得到 "" + "" = ""

【思考:】这个跟上面那题差不多,不过这个只存在一种小括号,就不用特意给每种括号元素定义值了,直接用一个计数器累计就可以

  • 遇到左括号,我们的计数器 +1+1,遇到右括号,我们的计数器 -1−1。这样的话,一组连续且有效的括号,将不会对计数器的值产生变化。

【代码:】

/**
 * @param {string} s
 * @return {string}
 */
var removeOuterParentheses = function(s) {
    let con = 0, ret = ''
    for(let i = 0; i < s.length; i++){
        if(s[i]==='(' && con++>0) ret+=s[i]
        if(s[i]===')' && con-->1) ret+=s[i]
    }
    return ret
};

验证栈序列

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

【示例1:】

输入:pushed = [1,2,3,4,5], popped = [4,5,3,2,1]
输出:true
解释:我们可以按以下顺序执行:
push(1), push(2), push(3), push(4), pop() -> 4,
push(5), pop() -> 5, pop() -> 3, pop() -> 2, pop() -> 1

【示例2:】

输入: pushed = [1,2,3,4,5], popped = [4,3,5,1,2]
输出: false
解释: 1 不能在 2 之前弹出。

【提示:】

  • 1 <= pushed.length <= 1000
  • 0 <= pushed[i] <= 1000
  • pushed 的所有元素 互不相同
  • popped.length == pushed.length
  • popped 是 pushed 的一个排列

【思考:】新建栈stack和计数器index,将pushed中元素依次推入栈,并循环popped,并判断stack中最后的元素和popped的index下标的元素是否相等,若相等,推出stack最后一个元素,同时计数器累加,最终stack中无元素,则返回true。

注意: popped中元素需判断是否存在,且其中0为有效值

【代码:】

/**
 * @param {number[]} pushed
 * @param {number[]} popped
 * @return {boolean}
 */
var validateStackSequences = function(pushed, popped) {
    let index = 0,stack = []
    for(let i = 0;i < pushed.length; i++) {
        stack.push(pushed[i])
        
        while(popped[index]!==undefined && popped[index] === stack[stack.length-1]){
            stack.pop()
            index++
        }
    }
    return !stack.length
};

移除无效的括号

题目】: 给你一个由 '('、')' 和小写字母组成的字符串 s。

你需要从字符串中删除最少数目的 '(' 或者 ')' (可以删除任意位置的括号),使得剩下的「括号字符串」有效。

请返回任意一个合法字符串。

有效「括号字符串」应当符合以下 任意一条 要求:

  • 空字符串或只包含小写字母的字符串
  • 可以被写作 AB(A 连接 B)的字符串,其中 A 和 B 都是有效「括号字符串」
  • 可以被写作 (A) 的字符串,其中 A 是一个有效的「括号字符串」

【示例1:】

输入:s = "lee(t(c)o)de)"
输出:"lee(t(c)o)de"
解释:"lee(t(co)de)" , "lee(t(c)ode)" 也是一个可行答案。

【示例2:】

输入: s = "a)b(c)d"
输出: "ab(c)d"

【示例3:】

输入: s = "))(("
输出: ""
解释: 空字符串也是有效的

【示例4:】

输入: s = "(a(b(c)d)"
输出: "a(b(c)d)"

【提示:】

  • 1 <= s.length <= 10^5
  • s[i] 可能是 '('')' 或英文小写字母

【方法:】首先首先定义一个空栈stack和包含所有字符的数组ret,用以保存括号,遍历字符串,利用入栈出栈去除ret中所有多余的‘)’,然后stack中只剩‘(’,此时再遍历stack去除ret多余的‘(’

【代码:】

/**
 * @param {string} s
 * @return {string}
 */
var minRemoveToMakeValid = function(s) {
    let ret = [...s]
    let stack = []
    for(let i = 0; i < s.length; i++) {
        if(ret[i]==='(') {
            stack.push(i)
        }
        if(ret[i]===')') {
            if(stack.length) {
                stack.pop()
            }else{
                ret[i]=''
            }
        }
    }
    for(let i = 0; i< stack.length; i++){
        ret[stack[i]] = ''
    }
    return ret.join('')
}

二叉树的后序遍历

题目】: 给定一个二叉树,返回它的 后序 遍历

【示例1:】

输入: [1,null,2,3]  
   1
    \
     2
    /
   3 

输出: [3,2,1]

进阶:  递归算法很简单,你可以通过迭代算法完成吗?

【思考:】后续遍历,即按照左右中的顺序遍历

  • 递归法:此方法较为简单,就是先排左,再排右,最后中间,每个节点都以此方法调用
  • 迭代法:首先设置一个栈stack,按照中右左保存每个节点,并在保存中节点时设置一个空标识位,设置答案空栈res,逆向循环stack中的节点,保存其中的标识位的下一个值,即为后序遍历的二叉树

【代码:】

// 递归法
/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @return {number[]}
 */
var postorderTraversal = function(root) {
    let res = []
    return _postorderTraversal(root, res)
};  


function _postorderTraversal(root, res) {
    if(!root) return res
    _postorderTraversal(root.left, res)
    _postorderTraversal(root.right, res)
    res.push(root.val)
    return res
}
// 迭代法
/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @description 迭代法
 * @param {TreeNode} root
 * @return {number[]}
 */
var postorderTraversal = function(root) {
    let stack = [],res = []
    if(root) stack.push(root)
    while(root!==null || stack.length!==0){
        root = stack.pop()
        if(root){
            stack.push(root)
            stack.push(null)

            if(root.right) stack.push(root.right)
            if(root.left) stack.push(root.left)
        }else{
            res.push(stack.pop().val)
        }
    }
    return res
};