括号匹配问题

ice_down的编程Tips——001

括号匹配问题

相关知识:栈、队列、switch、三目运算符

一、问题描述

1. 题目

The input string contains only 4 types of brackets, (), [], <> and {}. The goal is to determine whether each sequence of brackets is matched. If the input string has multiple levels of enclosure, the order of brackets must be <>, (), [], {} from inside to outside.

For example, if we input: [()], output should be ‘YES’, while input ([]) or [(], output should be ‘NO’.

Input

The first line of the input is an integer N, indicating how many strings below. The next N lines, each line is a string consisting of brackets and no longer than 255.

Output

There are N lines in the output, each line is either YES or NO.

Example
输入:
5
{}{}<><>()()[][]
{{}}{{}}<<>><<>>(())(())[[]][[]]
{{}}{{}}<<>><<>>(())(())[[]][[]]
{<>}{[]}<<<>><<>>>((<>))(())[[(<>)]][[]]
><}{{[]}<<<>><<>>>((<>))(())[[(<>)]][[]]

输出:
YES
YES
YES
YES
NO

2.大致意思

  • 输入只有四种括号:<>, (), [], {}
  • 判断条件:从小到大的顺序为<>, (), [], {} ,小括号不能套在比自己大的括号内
  • 输入的第一个数字n代表了接下来输入的行数,接下来输入n行括号序列
  • 判断每一行的括号序列是否符合判断条件的规则,符合则输出"YES",反之"NO",各成一行

二、大致思路

1.接收n行的数据
2.考虑判断过程中的点
3.对每行进行判断
4.根据判断结果输出

三、思路实现

1.接收n行的数据

将n行序列看做n行字符串进行存储

给小白的题外话:
Q:为什么我要用字符串呢?
A:因为字符串可以直接用索引得到字符串中的字符,我认为取用很方便,并且字符串的函数很多!能省好多事儿

考虑到n是一个变量,构造刚好能容纳n个字符串的容器可以想到三种方法

  1. 提前构造一个足够大的二维数组,这个可以,但是有点暴力不优雅
  2. 利用指针,害怕不会delete,用不熟练还容易各种溢出空指针
  3. 用队列存储,是一种简单的数据结构

我这里选择了用队列,会的就会,不会的可以略过这一块,反正我存进去了就对了,会有其他的篇目具体介绍它

//在main里面
int n;
queue<string> allExa;
//输入行数
cin >> n;
//输入n行string
for (int i = 0; i < n; i++) {
    string tem;
    cin >> tem;
    allExa.push(tem);
}
2.考虑判断过程中的点

这道编程题让小白防不胜防的地方就在这里,有很多的坑,接下来我用几个例子来表示

//1.奇数个括号,必定不能完成匹配,可以不用判断了
()()(
//2.小括号包大括号,不能完成匹配
({})
//3.右括号碰到了不匹配的左括号,不能完成匹配
(]
//4.虽然是偶数个,但是左右括号数不相等,最后肯定会剩下,不能完成匹配
()()((
3.对每行进行判断

我们这里准备了一个函数,去专门判断某一个字符串是否符合这里的判断条件,主要实现这样一个功能,下面会拆开说

bool isRight(string str) {
	int len = str.length();
	int *p = new int[len];
	stack<int> mem;
	for (int i = 0;i< len;i++)
	{
		switch (str[i])
		{
		case '<':p[i] = 1; break;
		case '(':p[i] = 2; break;
		case '[':p[i] = 3; break;
		case '{':p[i] = 4; break;
		case '>':p[i] = 5; break;
		case ')':p[i] = 6; break;
		case ']':p[i] = 7; break;
		case '}':p[i] = 8; break;
		default:
			len--;
			break;
		}
	}
	for (int i = 0;i<len;i++)
	{
		//right
		if (p[i] > 4) {
			if (mem.empty() || mem.top() != p[i] - 4)
				return false;
			else
				mem.pop();
		}//left
		else
		{
			if (mem.empty() || p[i] <= mem.top())
			{
				mem.push(p[i]);
			}
			else
				return false;
		}
	}
	return mem.empty() ? true : false;
}
  1. 第一步:将字符串全部转化为对应的数字,方便比较,以下是转换规则,1-4是左括号,5-8是右括号,数字大的括号就大,并且同样的括号左右差4,方便验证

    /*
    < : 1
    ( : 2
    [ : 3
    { : 4
    > : 5
    ) : 6
    ] : 7
    } : 8
    */
    	//调用length别忘了括号!!!
    	int len = str.length();
    	//构造存储转化后数字的数组
    	int *p = new int[len];
    	//声明用于匹配的容器mem(名字来源memory)
    	stack<int> mem;
    	for (int i = 0;i< len;i++)
    	{
    		switch (str[i])
    		{
    		case '<':p[i] = 1; break;
    		case '(':p[i] = 2; break;
    		case '[':p[i] = 3; break;
    		case '{':p[i] = 4; break;
    		case '>':p[i] = 5; break;
    		case ')':p[i] = 6; break;
    		case ']':p[i] = 7; break;
    		case '}':p[i] = 8; break;
    		default:
    			break;
    		}
    	}
    
  2. 第二步:开始操作刚刚存进去的数字

    1. 如果没有左括号直接来一个右括号,那怎么都是错的

      )
      
    2. 如果左括号后面来了一个不匹配的右括号,也是错的

      (]
      
    3. 如果左括号后面来了一个比它自己大的左括号,也是错的

      ({
      
    4. 有了上面的思路,我们来进行操作,如果下一个是

      • 左括号,符合条件,则加入栈中,等待匹配右括号
      • 右括号,符合条件,则带走它的左括号
    for (int i = 0;i<len;i++)
    {
    	//右括号的值大于4
    	if (p[i] > 4) {
            //我们检查栈顶的元素
            //如果栈是空的或者等待自己的不是它的左括号,返回false
    		if (mem.empty() || mem.top() != p[i] - 4)
    			return false;
            //如果是它的那个左括号,带走她,下一个
    		else
    			mem.pop();
    	}
        //左括号的值小于等于4
    	else
    	{
            //如果栈是空的,或者栈顶的左括号大于或等于自己,就加入他们
    		if (mem.empty() || p[i] <= mem.top())
    		{
    			mem.push(p[i]);
    		}
            //如果栈顶的括号比自己小,那就加不进去了,返回false
    		else
    			return false;
    	}
    }
    //当所有的数字都比较一遍后,还有剩下的,则返回false (二、2.4)
    return mem.empty() ? true : false;
    

    Q:return mem.empty() ? true : false;是啥?
    A: 这叫三目运算符,翻译成正常语句就是

    if(mem.empty() == true)
    	return true;
    else
        return false;
    

    其实return mem.empty();也同理,但是上方的语义性更强一点

4.根据判断结果输出
	for (int i = 0; i < n; i++) {
		cout << ((allExa.front().length() % 2 == 0 && isRight(allExa.front()) == true )? "YES" : "NO")<<endl;
		allExa.pop();
	}

不要害怕,一步步来解释

(allExa.front().length() % 2 == 0 && isRight(allExa.front()) == true )? "YES" : "NO"

这又是一个三目运算符

意思是(allExa.front().length() % 2 == 0 && isRight(allExa.front()) == true )成立则返回"YES",否则返回"NO"

allExa.front().length() % 2是用来判断字符串长度是否是偶数,如果是奇数则会直接短路&&后面的语句,直接返回false,从而输出"NO"

isRight(allExa.front()) == true是用来判断这个字符串是否符合我们的条件

四、源代码

#include <iostream>
#include <stack>
#include <queue>
#include <string>
using namespace std;
bool isRight(string str) {
	int len = str.length();
	int *p = new int[len];
	stack<int> mem;
	for (int i = 0;i< len;i++)
	{
		switch (str[i])
		{
		case '<':p[i] = 1; break;
		case '(':p[i] = 2; break;
		case '[':p[i] = 3; break;
		case '{':p[i] = 4; break;
		case '>':p[i] = 5; break;
		case ')':p[i] = 6; break;
		case ']':p[i] = 7; break;
		case '}':p[i] = 8; break;
		default:
			len--;
			break;
		}
	}
	for (int i = 0;i<len;i++)
	{
		//right
		if (p[i] > 4) {
			if (mem.empty() || mem.top() != p[i] - 4)
				return false;
			else
				mem.pop();
		}//left
		else
		{
			if (mem.empty() || p[i] <= mem.top())
			{
				mem.push(p[i]);
			}
			else
				return false;
		}
	}
	return mem.empty() ? true : false;
}
int main()
{
    /*
		< : 1
		( : 2
		[ : 3
		{ : 4
		> : 5
		) : 6
		] : 7
		} : 8
	*/
	int n;
	queue<string> allExa;
	cin >> n;
	for (int i = 0; i < n; i++) {
		string tem;
		cin >> tem;
		allExa.push(tem);
	}
	for (int i = 0; i < n; i++) {
		cout << 
			((allExa.front().length() % 2 == 0 && isRight(allExa.front()) == true )
				?"YES" : "NO")
			<<endl;
		allExa.pop();
	}
}

五、一点碎碎念

1.如有问题以及建议,欢迎联系邮箱:zpt艾特mail.nankai.edu.cn(为了防止被爬虫,艾特换为@)

2.也欢迎通过微信公众号:SoulDa 进行交流!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值