题目:括号匹配分析

给定字符串,输出括号是否匹配,例如,


  1. "()" yes;
  2. ")(" no;
  3. "(abcd(e)" no;
  4. "(a)(b)" yes。

要求必须用递归写,整个实现不可以出现一个循环语句。

分析

这个题目很多同学都见过了,如果没有后面的条件,会张口就说就来用栈来实现,时间复杂度O(n),空间复杂度O(n)。这个是很好的一个解答,没有 问题的。但是我们在做面试题,准备面试的过程中,每一个题目都不应该仅仅局限于某一个方法。应该尝试更多的思路,尽管有些思路的时间、空间复杂度并不是很 好,但是可以带来变化,举一反三,这才是真正的收获。

这个题要求了,只目能使用递归并且不能出现循环语句。这个时候,我们应该如何处理呢?其实告诉了大家递归,就比较好想了:怎么定义好问题和子问题。

如果字符串中的括号是匹配的,则'('的数量和')'的数量是相等的,反之是不相等的。这样,在递归的过程中,可以保存一个变量,用来记录'('的 数量和')'的数量是否匹配。这样定义递归问题f(p,count),表示当前字符p之前的字符串中'('的数量和')'的数量的匹配情况,p表示指向当 前字符的指针。初始的时候,f(p, 0),递归的过程如下:



  1. 如果p为空,则考察count是否为0,如果为0,则匹配;如果不为0,则不匹配;
  2. 如果不为空,则考察当前字符p,如果p='(',则递归调用f(p++, count++);如果p=')',则递归调用f(p++, count--)。如果p是其他的字符,并不是'('和')',则递归调用f(p++, count),count不变,继续考虑下一次字符。其中需要检查和保证count>=0.
     

其实,递归的问题有的时候不是那么好像的,需要大家不断的练习。如果不采用count来记录括号匹配的情况,这个题目的递归也不好想。

基于栈的代码:



bool isMatched(const char *str)
{
stack<char> s;
while(*str)
{
if(*str=='(')
{
s.push('(');
}
else if(*str==')')
{
if(!s.empty())//这里很重要,保证了如果输入)(,就直接退出
{
s.pop();

}
else
{
return false;
}
}
str++;
}
return s.empty();
}


递归的代码:下面是我刚开始写的,有什么错误吗?



bool isMatchedRec(char *p,int count)
{
if(*p=='\0')
{
return count==0;//如果p为空,则考察count是否为0,如果为0,则匹配;如果不为0,则不匹配;
}
else
{
if(*p=='(')
{
//return isMatchedRec(p++,count++); 错误,在递归内要写前缀++,不要写后缀,否则无穷递归。

return isMatchedRec(++p,++count);
}
else if(*p==')')
{
return isMatchedRec(++p,--count);
}
else
{
return isMatchedRec(++p,count);
}
}
}


错误在哪里;

假设我们输入“)(”,返回的还是true。

原因是没有没有检查count>=0.

正确的代码:



bool isMatchedRec(char *p,int count)
{
if(*p=='\0')
{
return count==0;//如果p为空,则考察count是否为0,如果为0,则匹配;如果不为0,则不匹配;
}
else
{
if(count>=0)
{
if(*p=='(')
{


return isMatchedRec(++p,++count);
}
else if(*p==')')
{
return isMatchedRec(++p,--count);
}
else
{
return isMatchedRec(++p,count);
}
}
else
return false;

}
}


可以把上面代码再简化下:



bool isMatchedRec2(const char* s,int count)
{
if(count<0)
return false;

if(*s=='\0')
{
return count==0;
}
else
{
if(*s=='(')
count++;
else if(*s==')')
count--;
}

return isMatchedRec2(++s,count);
}



扩展问题;

假设字符串中包含{,[,( 三种括号,请判断是否匹配。

http://blog.chinaunix.net/uid-28458801-id-3664897.html




bool isPair(char a,char b)
{
bool flag=true;
if(a=='{' && b=='}')
flag=true;
else if(a=='[' && b==']')
flag=true;
else if(a=='(' && b==')')
flag=true;
else
flag=false;

return flag;
}

bool isMatched(const char *str)
{
stack<char> s;
while(*str)
{
if(*str=='{' || *str=='[' || *str=='(' )
{
/*if( (s.top()=='{') || (s.top()=='['&&*str!='{') && (s.top()=='('&& *str=='(') )
s.push(*str);
else
return false;
*/
s.push(*str);

}
else if(*str=='}' || *str==']' || *str==')' )
{
if(!s.empty())//这里很重要,保证了如果输入)(,就直接退出
{
if(isPair(s.top(),*str))
{
s.pop();
}
else
{
return false;
}


}
else
{
return false;
}
}
str++;
}
return s.empty();
}



题目扩展

给你一个字符串,里面只包含"(",")","[","]","{","}"几种符号,请问你需要至少添加多少个括号才能使这些括号匹配起来。

例如:

[]是匹配的,所需括号个数为 0;([])[]是匹配的, 所需括号个数为 0;((]是不匹配的, 所需最少括号个数为 3;([)]是不匹配的,所需最少括号个数为 2.


这道变种题大多数人的解题思路都是动态规划,思路如下。不知道大家有没有好的别的方法,求教。

1)用 dp[i][j] 表示从位置 i 到字符位置 j 所需的最少括号数。假定字符串是 “[ ( )”, 那么 dp[0][0] = dp[1][1] = dp[2][2] = 1。


2)如果我们要算dp[i][j+1], 那么,最坏的情况是使得没有被匹配的括号数增加了,即 dp[i][j+1] 最多为 min( dp[i][j] + 1, dp[i+1][j+1] + 1). 但是,这可能不是我们想要的答案,因为在刚才的例子里,即:假定字符串是 “[ ( )”, 那么 dp[0][1] = dp[0][0] + 1= 2, 但是 dp[1][2] 却不等于 dp[1][1] + 1.


3)那么,什么情况下dp[i][j+1] = dp[i][j] + 1?只有当 字符串里从i 到 j 没有任何字符与第 j + 1 个字符匹配的时候。但是,如果存在和第 j + 1 个字符匹配的情况,问题就不一样了。


4)假设在i 到 j 之间存在一个字符(比如在位置 k)与第 j + 1 个字符匹配,那么我们相当于把原来的字符串分成了两个部分dp[i][k-1] 和 dp[k+1][j], 因为第k 个 和 j + 1 个字符已经匹配掉了。而且,我们不会再考虑 i 到 k - 1 的字符会和 k + 1 到 j 之间的字符匹配的情况,因为我们已经把这两个部分完全分开了。话句话说 dp[i][j+1] = min(min( dp[i][j] + 1, dp[i+1][j+1] + 1), dp[i][k-1] + dp[k+1][j]).



#include<iostream>
#include<string>
#include<memory.h>
using namespace std;
bool is(char a, char b){
if(a == '(' && b == ')')
return 1;
if(a == '[' && b == ']')
return 1;
if(a == '{' && b == '}')
return 1;
return 0;
}
int main(){
//dp[i][j] 表示从第i位至第j位的最小匹配长度
int t, i, j, k, dp[105][105];
cin >> t;
while(t--){
string s;
cin >> s;
memset(dp, 0, sizeof(dp));
for(i = 0; i <= s.length(); ++i){
dp[i][i] = 1;
}
for(i = 2; i <= s.length(); ++i){
for(j = i - 1; j >= 1; --j){
dp[j][i] = dp[j][i - 1] + 1;
for(k = j; k < i; ++k){
if(is(s[k - 1], s[i - 1])){
dp[j][i] = min(dp[j][i], dp[j][k - 1] + dp[k + 1][i - 1]);
}
}
}
}
cout << dp[1][s.length()] << endl;
}
return 0;
}


改写的更容易看懂 的代码:

dp[i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j])k [i,j)



bool isPair(char a,char b)
{
if(a == '(' && b == ')')
return 1;
if(a == '[' && b == ']')
return 1;
if(a == '{' && b == '}')
return 1;
return 0;
}

int match(int dp[100][100],char s[])
{
for(int i=0;i<100;i++)
for(int j=0;j<100;j++)
dp[i][j]=0;

int len=strlen(s);
for(int i=0;i<=len;i++)
dp[i][i]=1;

for(int j=2;j<=len;j++)
{
for(int i=j-1;i>=1;i--) //i从1开始,说明dp[0]不存
{
dp[i][j]=dp[i][j-1]+1;
for(int k=i;k<j;k++)
{
if(isPair(s[k-1],s[j-1]))
{
dp[i][j]=min(dp[i][j],dp[i][k-1]+dp[k+1][j-1]);
}
}

}
}
return dp[1][len];
}


递归版:



//递归版
#include<stdio.h>
#include<string.h>

const int inf = 0x0fffffff;
#define min(x, y)(x < y ? x : y)
#define low(x, y)((x == '(' && y == ')') || (x == '[' && y == ']'))
char str[110];
int dp[110][110];
int fun(int x,int y)
{
if(dp[x][y] != -1)
return dp[x][y];
if(x > y)return 0;
if(x == y)return 1;
int ans = inf;
if(low(str[x], str[y]))
ans = min(ans, fun(x + 1, y - 1));
for(int i = x;i < y; ++ i)
ans = min(ans, fun(x, i) + fun(i + 1, y));
dp[x][y] = ans;
return ans;
}

int main()
{
int T;
scanf("%d", &T);
while(T --)
{
scanf("%s", str);
int len = strlen(str);
memset(dp, -1, sizeof(dp));
printf("%d\n", fun(0, len - 1));
}
}


参考:http://blog.acmj1991.com/?p=1142

http://www.dewen.org/q/8653/%E4%B8%80%E4%B8%AA%E6%8B%AC%E5%8F%B7%E5%8C%B9%E9%85%8D%E7%9A%84%E6%89%A9%E5%B1%95%E9%97%AE%E9%A2%98

另一题:

输入括号的数目,输出括号的各种合法匹配样式

输入 2

输出

(())

()()

据说这是一道某公司的面试题,我们先来分析一下。括号匹配有合法有的不合法 如 (()))( 这样就不是合法的匹配样式。为了避免这种情况的出现,记录当前左括号的个数和右括号的个数,使右括号的个数不大于左括号的个数。主要思想类似于0-1背包问题,当进行到某一步的时候 有两种方法:放'(' 和 放 ')'



void matching(int left,int right,int sum,vector<char> bracket)
{
if (left==sum&&right==sum) //如果左边和右边都为要匹配的个数,则输出结果
{
vector<char>::iterator iter=bracket.begin();
for ( ;iter!=bracket.end();iter++)
{
cout<<*iter<<' ';
}
cout<<endl;
return ;
}
if (left<=sum)
{
bracket.push_back('('); //放入左括号,然后递归
matching(left+1,right,sum,bracket);
bracket.pop_back(); //递归后弹出左括号
}

if (left>right&&left<=sum)
{
bracket.push_back(')');
matching(left,right+1,sum,bracket);
bracket.pop_back();
}
}
int main(int argc, char* argv[])
{
vector<char> bracket; //记录当前的匹配样式
int num;
cin>>num; //输入括号的个数
matching(0,0,num,bracket);
return 0;
}


我最开始想的是回溯法,应该和八皇后问题思路一致。但这个程序不是。上面递归程序不好理解。

可以举个例子:sum=3时,

面试题之括号匹配分析( 出栈序列是否合法,给定一个入栈序列,求所有可能的出栈序列等等)_出栈

最开始一值是运行第二个递归:

(((,

接着:

放入一个),接着递归,又放入一个),又放入); 最后变成了:

((()));

输出这个之后。

给定一个入栈序列,求所有可能的出栈序列

有2n个人排成一队进入剧场。入场费5元。其中只有n个人有一张5元钞票,另外n人只有10元钞票,剧院无其它钞票可找零,问有多少中方法使得只要有10元的人买票,售票处就有5元的钞票找零?(将持5元者到达视作将5元入栈,持10元者到达视作使栈中某5元出栈)。

对于这个例子,剧院要想总有零钱可找,那么目前进入剧院的人数中,揣着10元钞票的人数必须少于等于揣着5元钞票的,不然肯定在某个人那出现没零钱找的情况。

现在回到正题上来对于一个给定入栈序列,怎么求它的出栈序列呢?

我们可以把入栈记为1,出栈记为0.那么前缀子序列中1的个数必须大于等于0的个数,即入栈次数要大于等于出栈次数,如1 1 0 1 0 0,它的任意前缀序列中1的个数是大于等于0的个数的。

我们来看个例子:对于1 2 3这个入栈序列,1 1 0 1 0 0就是一个入栈出栈序列,第一个1代表元素1入栈,然后第二个1代表元素2入栈,然后第三个是0,代表出栈,即元素2出栈,然后第四个是1,代表元素3入栈,然后第五个是0,代表出栈,即元素3出栈,然后第六个是0,代表元素1出栈。最后1 1 0 1 0 0就代表了出栈序列2 3 1。

那么现在的问题就转换为如何求出所有符合条件的0 1序列了。其实这和以下问题相同:给定括号对数,输出所有符合要求的序列。如2对括号,输出有()()或者(())两种。1可以看成'(',0可以看成‘)’。

下面贴上本人的程序,并给出详细注释。

#include <iostream>

#include <vector>

using namespace std;



void func(vector<char>kind,int count[],int n)

{

if(count[0]>=1)

{

kind.push_back('(');

count[0]--;

func(kind,count,n);

count[0]++;

kind.pop_back();

}

if((count[1]>=1) && (count[1]>count[0]))

{

kind.push_back(')');

count[1]--;

func(kind,count,n);

count[1]++;

kind.pop_back();

}

if(kind.size()==2*n)

{

vector<char>::iterator iter;

for(iter=kind.begin();iter!=kind.end();iter++)

{

cout<<(*iter)<<" ";

}

cout<<endl;

}

}



int main()

{

int n;

cout << "please input the number of ():" << endl;

cin>>n;

int count[2]={n-1,n};

vector<char>kind;

kind.push_back('(');

func(kind,count,n);

return 0;

}

count[0]存着左括号数目,count[1]存着右括号数目。一开始kind中压入左括号,因为第一个肯定是左括号。然后count数组初始化为n-1个左括号,n个右括号。然后我们递归的处理。如果剩余左括号数count[0]大于0,就可以把左括号压栈。而对于右括号,栈中左括号个数必须多于右括号个数,也就是剩余右括号个数大于左括号个数,即count[1]>count[0]时,才能将右括号压栈。如果栈中元素个数达到2n时,就把栈中元素输出。

下面贴出出栈序列代码,几乎和上面相同。

#include <iostream>

#include <stack>

#include <vector>

using namespace std;



int number=0;

void func(vector<int>kind,int count[],int n,int A[])

{

if(count[0]>=1)

{

kind.push_back(1);

count[0]--;

func(kind,count,n,A);

count[0]++;

kind.pop_back();

}

if((count[1]>=1) && (count[1]>count[0]))

{

kind.push_back(0);

count[1]--;

func(kind,count,n,A);

count[1]++;

kind.pop_back();

}

if(kind.size()==2*n)

{

vector<int>::iterator iter;

stack<int>stk;

int j=0;

for(iter=kind.begin();iter!=kind.end();iter++)

{

//cout<<(*iter)<<" ";

if(1==(*iter))

{

stk.push(A[j]);

j++;

}

else

{

cout<<stk.top()<<" ";

stk.pop();

}

}

number++;

cout<<endl;

}

}



int main()

{

int n,i;

cout << "please input the number:" << endl;

cin>>n;

int A[n];

cout << "please input the push sequence:" << endl;

for(i=0;i<n;i++)

{

cin>>A[i];

}

int count[2]={n-1,n};

vector<int>kind;

kind.push_back(1);

cout<<"the result is:"<<endl;

func(kind,count,n,A);

cout<<"total:"<<number<<endl;

return 0;

}


一篇文章:



#include<iostream>
#include<stack>
using namespace std;

static int counter;

void outprint(stack<int> q){
while (q.size() != 0) {
cout << q.top() << "-> ";
q.pop();
}
cout << endl;
counter++;
return;
}

//q 存放入栈序列
//stk 用于模拟入栈过程
//output 用于存放可能的出栈序列
void allPopSeq(stack<int> q,stack<int> stk,stack<int> output){
if((q.size() == 0)&&(stk.size()==0)&&(output.size() == 5)) {
outprint(output);
return;
}
if(q.size()!=0){//入栈
int v = q.top();
stk.push(v);
q.pop();
allPopSeq(q,stk,output);
stk.pop();
q.push(v);//回溯恢复
}
if(stk.size()!=0) //出栈
{
int v = stk.top();
stk.pop();
output.push(v);
allPopSeq(q,stk,output);
output.pop();
stk.push(v);//回溯恢复
}
return;
}
int main()
{
int arr[5] = { 1, 2, 3, 4, 5 };
stack<int> stkValues;
stack<int> stkOutput;
stack<int> tmp;
int i;
for (i = 0; i != 5; ++i){
stkValues.push(arr[i]);
}
allPopSeq(stkValues, tmp, stkOutput);
cout << counter << endl;
}


程序员面试题精选100题(24)-栈的push、pop序列[数据结构]

题目:输入两个整数序列。其中一个序列表示栈的push顺序,判断另一个序列有没有可能是对应的pop顺序。为了简单起见,我们假设push序列的任意两个整数都是不相等的。

比如输入的push序列是1、2、3、4、5,那么4、5、3、2、1就有可能是一个pop系列。因为可以有如下的push和pop序列:push 1,push 2,push 3,push 4,pop,push 5,pop,pop,pop,pop,这样得到的pop序列就是4、5、3、2、1。但序列4、3、5、1、2就不可能是push序列1、2、3、4、5的pop序列。

分析:这到题除了考查对栈这一基本数据结构的理解,还能考查我们的分析能力。

这道题的一个很直观的想法就是建立一个辅助栈,每次push的时候就把一个整数push进入这个辅助栈,同样需要pop的时候就把该栈的栈顶整数pop出来。

我们以前面的序列4、5、3、2、1为例。第一个希望被pop出来的数字是4,因此4需要先push到栈里面。由于push的顺序已经由push序列确定了,也就是在把4 push进栈之前,数字1,2,3都需要push到栈里面。此时栈里的包含4个数字,分别是1,2,3,4,其中4位于栈顶。把4 pop出栈后,剩下三个数字1,2,3。接下来希望被pop的是5,由于仍然不是栈顶数字,我们接着在push序列中4以后的数字中寻找。找到数字5后再一次push进栈,这个时候5就是位于栈顶,可以被pop出来。接下来希望被pop的三个数字是3,2,1。每次操作前都位于栈顶,直接pop即可。

再来看序列4、3、5、1、2。pop数字4的情况和前面一样。把4 pop出来之后,3位于栈顶,直接pop。接下来希望pop的数字是5,由于5不是栈顶数字,我们到push序列中没有被push进栈的数字中去搜索该数字,幸运的时候能够找到5,于是把5 push进入栈。此时pop 5之后,栈内包含两个数字1、2,其中2位于栈顶。这个时候希望pop的数字是1,由于不是栈顶数字,我们需要到push序列中还没有被push进栈的数字中去搜索该数字。但此时push序列中所有数字都已被push进入栈,因此该序列不可能是一个pop序列。

也就是说,如果我们希望pop的数字正好是栈顶数字,直接pop出栈即可;如果希望pop的数字目前不在栈顶,我们就到push序列中还没有被push到栈里的数字中去搜索这个数字,并把在它之前的所有数字都push进栈。如果所有的数字都被push进栈仍然没有找到这个数字,表明该序列不可能是一个pop序列。

基于前面的分析,我们可以写出如下的参考代码:



#include <stack>

/////////////////////////////////////////////////////////////////////////////
// Given a push order of a stack, determine whether an array is possible to
// be its corresponding pop order
// Input: pPush - an array of integers, the push order
// pPop - an array of integers, the pop order
// nLength - the length of pPush and pPop
// Output: If pPop is possible to be the pop order of pPush, return true.
// Otherwise return false
/////////////////////////////////////////////////////////////////////////////
bool IsPossiblePopOrder(const int* pPush, const int* pPop, int nLength)
{
bool bPossible = false;

if(pPush && pPop && nLength > 0)
{
const int *pNextPush = pPush;
const int *pNextPop = pPop;

// ancillary stack
std::stack<int> stackData;

// check every integers in pPop
while(pNextPop - pPop < nLength)
{
// while the top of the ancillary stack is not the integer
// to be poped, try to push some integers into the stack
while(stackData.empty() || stackData.top() != *pNextPop)
{
// pNextPush == NULL means all integers have been
// pushed into the stack, can't push any longer
if(!pNextPush)
break;

stackData.push(*pNextPush);

// if there are integers left in pPush, move
// pNextPush forward, otherwise set it to be NULL
if(pNextPush - pPush < nLength - 1)
pNextPush ++;
else
pNextPush = NULL;
}

// After pushing, the top of stack is still not same as
// pPextPop, pPextPop is not in a pop sequence
// corresponding to pPush
if(stackData.top() != *pNextPop)
break;

// Check the next integer in pPop
stackData.pop();
pNextPop ++;
}

// if all integers in pPop have been check successfully,
// pPop is a pop sequence corresponding to pPush
if(stackData.empty() && pNextPop - pPop == nLength)
bPossible = true;
}

return bPossible;
}


​http://zhedahht.blog.163.com/blog/static/25411174200732102055385/​

或者判断出栈序列是否合法

输入两个整形序列,*put,*out,分别表示入栈和出栈,判断出栈是否符和入栈要求。


假设入栈为 首先1,2,3,4,5.出栈顺序为4,3,5,1,2


分析:要得到4,则需将1,2,3,4全部入栈,然后pop得到4,输出3,因为栈顶元素即为3,直接输出即可。如果要得到5,需要将入栈队列中的5压入栈,然后输出。此时入栈队列已经为空。栈顶元素为2,底为1.此时需要输出1,但是要先输出2,错误,则此出栈顺序不合理。




即如果元素在栈顶,直接pop得到,若不在栈顶,需要从入栈队列找到她,一直入栈,然后在pop。否则无法顺利。




代码: 这个代码写的还可以


bool Judge(int *put,int *out,int len)


{


bool val=false;


if(put&&out&&len>0)


{


int *p=put;


int *q=out;


stack<int> a;


while(q-out<len)


{


while(a.empty()||*q!=a.top()) //如果栈为空或者栈顶元素不等于*q 则需寻找


{


if(!p)


break;


a.push(*p);


if(p-put<len) //判断入栈队列是否含有元素


p++;


else


p=NULL;




}


if(*q!=a.top()) //全部入栈后还是不等


break;


a.pop();


q++; //判断下一个


}


if(a.empty()&&(q-out==len))


val=true;


}


return val;


}






扩展,给出一组数字为出栈队列,求其可能的入栈队列。




分析:可以根据这组数字,全排列出所有组合,然后每一个组合利用上面的函数判别即可,如果true 即输出。




代码:


void fun(int *a,int *begin,int n,int *b) //全排列求解


{


if(begin-a==n)


{


if(Judge(a,b,n)


for(int i=0;i<n;i++)


cout<<a[i]<<" 满足条件!!"<<endl;


}


else


{


for(int *temp=beign;temp-a<n;temp++)


{


int val=*begin;


*begin=*temp;


*temp=val;


fun(a,begin+1,n,b);


*temp=*begin;


*begin=val;


}


}




}




void Show(int *b,int n)


{


int *a=new int[n];


for(int i=0;i<n;i++)


a[i]=n[i];


fun(a,a,n,b);


delete []a;


}






int main()


{


int b[]={4,5,3,2,1};


Show(b,5);


}