当前位置 博文首页 > Valid Parentheses_让代码改变世界:LeetCode 20

    Valid Parentheses_让代码改变世界:LeetCode 20

    作者:[db:作者] 时间:2021-07-10 18:55

    这么多天了,才刷到20题,真是伤心啊,不过毕竟坚持了下来了,也算对得起自己了,这道题的难度也是简单,不过倒是挺有意思的,实现的方式也比较多,用来学习算法还是不错的。

    Given a string containing just the characters?'(',?')',?'{',?'}',?'['?and?']', determine if the input string is valid.

    The brackets must close in the correct order,?"()"?and?"()[]{}"?are all valid but?"(]"?and?"([)]"?are not.

    题目用一句话来说就是括号匹配,给定一个由圆括号、大括号、方括号组成的字符串,判断其是否可以配对成功,其实有过编程经验的人都知道括号匹配的问题,写代码的时候也要注意这些东西,如果写的不匹配了一般的编译器都会提示错误的,那编译器是怎么判断括号是否配对成功的呢,今天就来让你实现以下匹配的算法。

    括号的匹配规则用一句话说就是和最近的一个可以匹配的匹配,可以嵌套但不能重叠。也就是说“([]{})”是可以的,但”({)}“是不可以的。

    该怎么考虑呢?这就看你有多少经验了,要是没有任何经验就凭自己想是有点儿困难的,不过我们也不要放弃,多动脑子是没有坏处的,这次我就把自己的探索经历给大家分享一下,希望各位也可以从中得到一些启发,更重要的是培养自主思考的能力。

    首先,这是一个配对问题,要配对就肯定要找一对嘛,怎么找呢?给你一个”括号串“,你首先拿到了第一个”半括号“,这个半括号如果是右括号那肯定就直接输出false了,如果是左括号呢?你去哪儿找它的另一半儿呢?紧挨着从第二个往后找?因为可以嵌套,如果一连几个都是左括号,而且有同型的的话该怎么办呢?你遇到的第一个可以匹配的右括号是该跟第一个匹配还是跟后面的同型的匹配呢?应该跟最后一个同型的匹配。那他俩配完了他俩中间的呢?万一是一个半括号那就false,要不是还要做其他判断。。。好像有点儿复杂。那从字符串尾开始找呢?显然更不靠谱了,倒数第一个右括号应该跟它前面倒数第一个左括号匹配,跟第一个左括号很可能没有一毛钱的关系。。。显然这种拿到第一个找其”对象“的思路是不好使的。

    虽然第一步探索失败了,但从中我们发现一个点,那就是第一个右括号是一定和它前面的同型左括号相匹配的。我们自然有了这样的想法,如果先找到第一个右括号然后找它的”对象“呢?也就是找到第一个右括号往前面找它的”对象“,这里的一个问题就是找的过程中怎么处理其他左括号,显然第一个右括号肯定要紧挨着其”对象“,否则一定会有半个括号或者重叠括号,可以直接输出false。那怎么处理嵌套情况呢?这里就要看大家有没有这样的经历了,最直接的想法就是把已经配对的括号”删去“。如果想到这一点,说明你离成功只差最后一步了,因为算法已经设计完成了:找到第一个右括号,判断其是否存在对象,不存在就返回false存在就删除这一对,然后找第二个右括号。。。

    既然算法设计已经完成,那还差哪一步呢?找右括号显然不是问题,找对象也比较容易解决,找不到返回false也不用多说,,,,没错,就是删除这一对有点儿问题,按理说这已经是程序设计以及语言特性的问题了。不同的程序设计者有不同的策略,不同的语言也有不同的方法,你也可以设计一个可以删除元素的string类型,那问题就解决了。我们这里不做这种事情,而是讲两种比较通用的思想:测试表和栈。

    测试表是我自己的实现思路,就是构建一个测试表,分别对应每一个半括号,如果括号已经配对成功,那么则将表中对应位设为true(对应上面提到的删除括号),在历遍括号string的过程中根据测试表判断是否可以跳过该括号(配完对的可以跳过)。到最后判断是否测试位全部为true,如果全为true证明所有括号都匹配成功,否则证明匹配失败。对应的代码如下:

    class Solution {
    public:
        bool isValid(string s) {
    		int len=s.size();
    		if(len==0) return true;
    		if(s[0]==')'||s[0]=='}'||s[0]==']') return false;
    		vector<bool> test(s.size(),false);//测试表,全部为0
    		int lef=0,rig=0;
    		while(rig<len)
    		{
    			//跳过所有左括号
    			if(s[rig]=='('||s[rig]=='{'||s[rig]=='[')
    				rig++;
    			else
    			{
    				lef=rig-1;//指向右括号左边一个
    				while(test[lef]==true && lef>0)lef--;//跳过所有已经匹配过的括号
    				if(lef<0)return false;
    				if(( s[lef]=='('&&s[rig]==')') || 
    					(s[lef]=='{'&&s[rig]=='}') || 
    					(s[lef]=='['&&s[rig]==']') )
    				{ 
    					test[lef]=test[rig]=true;rig++; 
    				}
    				else return false;
    			}
    		}
    		for(int i=0;i<len;++i)
    			if(test[i]==false)return false;
    		return true;
        }
    };
    代码没什么可说的,注意边缘输入就可以了。

    第二种是从大神那儿学来的,就是利用栈这种数据结构来实现算法,经过分析发现栈这种东西就是为这种算法而生的,二者契合度为百分之九十。这个就不解释了,留给大家自己分析吧,把代码奉上,在这里感谢?TJUYZL大神的分享

    class Solution {
    public:
        bool isValid(string s) {
            int n = s.size();
            if(n==0) return true;
            if(n%2==1) return false;
            stack<char> str;
            str.push(s.at(0));
            int i=1;
            while(i<n){
                if(s.at(i)=='(' ||s.at(i)=='['||s.at(i)=='{'){
                    str.push(s.at(i));
                }
                else{
                    char tmp = str.top();
                    if(valid(tmp,s.at(i))){
                        str.pop();
                    }
                    else{
                        return false;
                    }
                }
    
                    i++;
            }
            if(str.empty()) 
                return true;
            else
                return false;
        }
    
        bool valid(char a, char b){
            if((a=='(' && b==')') || (a=='[' && b==']') || (a=='{' && b=='}'))
                return true;
            else
                return false;
    
        }
    };
    以后碰到类似的题,希望大家可以想到栈这种数据结构,还是那句话,数据结构这门课是程序员的内功心法,是必须要学的,另外测试表的用处可能更灵活一点,试用的情况也比较多,大家最好也理解一下。

    正如前面所说,算法设计和程序设计在本题中是分开进行的,两种代码其实是用的一种算法,大家可以仔细考虑一下。吃饭的时间快到了,不多说了


    cs