题目描述

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

有效字符串需满足:

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

示例 1:

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

示例 2:

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

示例 3:

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

示例 4:

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

示例 5:

输入:s = "{[]}"
输出:true
力扣原题目地址:https://leetcode.cn/problems/...

思路分析

阅读了以上题目的要求以后,我们大致可以明白一下几点:

  • 首先,括号是成对出现的,有左括号,就有对应的右括号,所以括号字符串的长度一定是2的倍数即偶数,才符合基本条件,即:s.length % 2 === 0 取模2等于1就是奇数了,肯定是不行的
  • 当出现一个括号的时候,在未来的某一时刻,会出现另一个对的括号,与他成双成对。所以咱们也可以换种方式理解,更有助于我们解题

    换种方式理解

    这个比喻不太恰当,不过有助于理解题目

仓库(字符串)有一堆鞋子(括号),有大人的鞋子(大括号)、和小孩的鞋子(小括号),鞋子嘛,有左脚和右脚(左括号和右括号)。老板让咱给鞋子做左脚鞋和右脚鞋配对

这里得加个条件:通过左鞋可以匹配到对应右鞋,反之不行。咱要是问啥?因为括号匹配只能是先左后右,没有先右括号,后左括号的

于是我们遍历这个仓库,一只一只把鞋子拿出来。用笔在一张纸上记下当前这个鞋子需要匹配的另外的鞋子是啥型号的(纸张记下匹配需求),记录好以后,我们就把这张纸,放在一个不用的鞋盒子里(入栈) ,如果一直记录下去,鞋盒子里的纸会有很多张,甚至鞋盒子装不下了(栈溢出),所以我们需要对比看看后续拿到的鞋子,是不是这个鞋盒子里上面哪张纸上写的需要的鞋子(看看能不能匹配上),能匹配上,就把鞋盒子里这张纸,拿出来(出栈),说明匹配成功;匹配不上,就把继续抽张纸,写上手里的这个鞋子,需求的另一只鞋子是啥型号的(继续入栈),如此操作,直至操作完。

操作完以后,再看看鞋盒子里有没有纸张,有的话,说明有鞋子没匹配上。没有纸张,就说明都匹配上了

大家发挥自己的想象力,设想一下这种场景,,这个场景理解了的话,再看下方的代码,就很好理解了

使用栈数组去解决

var isValid = function (s) {
    // 1. 如果是奇数个,肯定是不符合条件的,因为括号是成对成对出现的
    if (s.length % 2 === 1) {
        return false
    }
    // 2. 如果是偶数个,再进一步做判断,看是否符合
    let stackArr = [] // 3. 定义一个栈(数组)用来存放配对需要的标识
    for (let i = 0; i < s.length; i++) { // 4. 遍历拿到字符串中每一个括号
        // 5. 一开始栈数组是空的,所以不用对比,继续往下看
        if (stackArr[stackArr.length - 1] === s[i]) {
            stackArr.pop()
        } 
        // 6. 接下来根据遇到的是什么括号,往栈数组中入栈所需要的另一半括号
        else {
            if (s[i] === '(') { // 7. 如果是左小括号,就在栈数组中追加一个标记,需要右小括号,进行搭配
                stackArr.push(')')
            } else if (s[i] === '[') { // 8. 如果是左中括号,就在栈数组中追加一个标记,需要右中括号,进行搭配
                stackArr.push(']')
            } else if (s[i] === '{') { // 9. 如果是左大括号,就在栈数组中追加一个标记,需要右大括号,进行搭配
                stackArr.push('}')
            } else { // 10. 如果直接就直接遇到了右小括号、右中括号、右大括号,那就说明是有问题的,因为只能是左匹配右,不能右匹配左
                return false // 11. 比如咱们正常使用括号()  []  {} 从来不会这样用 )(   ][   }{
                // 12. 加之上方的if判断,是不能出现这种情况的,如果出现了,就说明括号字符串是有问题的,直接false即可
            }
        }
    }
    // 13. 匹配完了以后,看看是否有没匹配上的
    if (stackArr.length == 0) { // 14. 要是都匹配上了,说明就是没问题的,返回true
        return true
    } else { // 15. 反之,说明不符合
        return false
    }
};

使用Map集合做代码精简

可以理解为使用Map集合创建一个需求匹配字典

var isValid = function (s) {
    if (s.length % 2 === 1) {
        return false
    }
    let map = new Map() // 创建一个map集合
    map.set('(', ')') // 左小括号匹配右小括号
    map.set('[', ']') // 左中括号匹配右中括号
    map.set('{', '}') // 左大括号匹配右大括号
    let stackArr = []
    for (let i = 0; i < s.length; i++) {
        if (stackArr[stackArr.length - 1] === s[i]) {
            stackArr.pop()
        }
        else {
            if (map.has(s[i])) { // 若当下的这个半拉括号在map集合中有,即要么( 要么[ 要么{
                let need = map.get(s[i]) // 那就拿到对应所需要的另一半的值,要么) 要么] 要么}
                stackArr.push(need) // 然后把入栈告知需要的另一半括号是啥,等待匹配之
            } else { // 如果map集合中没有,说明就是遇到了) 或] 或},那就是有问题的,所以直接返回false
                return false
            }
        }
    }
    if (stackArr.length == 0) {
        return true
    } else {
        return false
    }
};

js中关于栈的简单理解

我们知道数组比较灵活,可以头部追加元素unshift头部删除元素shift尾部追加元素push尾部删除元素pop,或者直接splice想怎么操作就怎么操作。但是栈不行。

可以想象一下,数组平常是横着的,而栈就是竖着的,立起来的。所以只有上方一个入口了,追加元素或者删除元素,都只能通过上方的一个入口,进行操作。

栈是被限制的数组,也可以称之为栈数组

也就是说,栈数组只能push尾部追加pop尾部删除 ,即先追加进来的元素,只能后删除;后追加进来的元素,可以先删除。即:先进后出,或后进先出

至于队列,其实也是被限制的数组,也可以称之为队列数组,篇幅原因,在此不赘述

最后抛出一个观点,咱们做算法题的时候,不一定非要和leetcode官方的解法,一模一样。只要思路一致即可,毕竟万变不离其宗,思路很重要哦^_^


水冗水孚
1.1k 声望588 粉丝

每一个不曾起舞的日子,都是对生命的辜负