面试题之括号匹配分析( 出栈序列是否合法,给定一个入栈序列,求所有可能的出栈序列等等) 题目:括号匹配分析 给定字符串,输出括号是否匹配,例如, ”()” yes; ”)(” no; ”(abcd(e)” no; ”(a)(b)” yes。 要求必须用递归写,整个实现不可以出现一个循环语句。 分析 这个题目很多同学都见过了,如果没有后面的条件,会张口就说就来用栈来实现,时间复杂度O(n),空间复杂度O(n)。这个是很好的一个解答,没有 问题的。但是我们在做面试题,准备面试的过程中,每一个题目都不应该仅仅局限于某一个方法。应该尝试更多的思路,尽管有些思路的时间、空间复杂度并不是很 好,但是可以带来变化,举一反三,这才是真正的收获。 这个题要求了,只目能使用递归并且不能出现循环语句。这个时候,我们应该如何处理呢?其实告诉了大家递归,就比较好想了:怎么定义好问题和子问题。 如果字符串中的括号是匹配的,则'(‘的数量和’)’的数量是相等的,反之是不相等的。这样,在递归的过程中,可以保存一个变量,用来记录'(‘的 数量和’)’的数量是否匹配。这样定义递归问题f(p,count),表示当前字符p之前的字符串中'(‘的数量和’)’的数量的匹配情况,p表示指向当 前字符的指针。初始的时候,f(p, 0),递归的过程如下: 如果p为空,则考察count是否为0,如果为0,则匹配;如果不为0,则不匹配; 如果不为空,则考察当前字符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.csdn.net/buaa_shang/article/details/11726619 扩展问题; 假设字符串中包含{,[,( 三种括号,请判断是否匹配。 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 ”, 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; } 参考:http://blog.csdn.net/zz198808/article/details/7585385 一篇文章: #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; } http://www.cnblogs.com/fxplove/articles/2500898.html 程序员面试题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); } 参考: http://blog.163.com/m13591120447_1/blog/static/21637918920132270746190/ http://blog.sina.com.cn/s/blog_4ac9f56e01014vcs.html http://blog.chinaunix.net/uid-21712186-id-1818123.html
2024最新激活全家桶教程,稳定运行到2099年,请移步至置顶文章:https://sigusoft.com/99576.html
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。 文章由激活谷谷主-小谷整理,转载请注明出处:https://sigusoft.com/48137.html