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个字符串的容器可以想到三种方法
- 提前构造一个足够大的二维数组,这个可以,但是有点暴力不优雅
- 利用指针,害怕不会delete,用不熟练还容易各种溢出空指针
- 用队列存储,是一种简单的数据结构
我这里选择了用队列,会的就会,不会的可以略过这一块,反正我存进去了就对了,会有其他的篇目具体介绍它
//在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-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; } }
-
第二步:开始操作刚刚存进去的数字
-
如果没有左括号直接来一个右括号,那怎么都是错的
)
-
如果左括号后面来了一个不匹配的右括号,也是错的
(]
-
如果左括号后面来了一个比它自己大的左括号,也是错的
({
-
有了上面的思路,我们来进行操作,如果下一个是
- 左括号,符合条件,则加入栈中,等待匹配右括号
- 右括号,符合条件,则带走它的左括号
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 进行交流!