在技術筆試面試上,我們常常會遇到這樣一類題型,如給你一個入棧序列,然後再讓你判斷幾個序列是否有可能爲它的出棧序列,如:
入棧序列爲 1 2 3 4 5,則 1 2 3 4 5可能爲它的出棧序列,而 5 4 1 2 3不可能爲它的出棧序列。
對於n比較小的情況,我們往往可以通過手動模擬的方式來判斷,對於n比較大的時候,這種方法就顯得效率不佳了。
下面介紹一種通用的方法判定合法出棧序列,時間複雜度爲O(n)。爲了敘述方便,我們不妨設入棧序列爲 1 2 3.......n,並且每個元素各不相等。
事實上,一個出棧序列固定的話,那麼沒個數的出棧順序和時間都是固定的,則我們可以模擬棧的入棧出棧過程,來判斷是否一個合法的出棧序列。
我們首先設po爲目前爲止入棧的元素中最大的數,初始化爲0,若下一個出棧元素要大於po的話(設爲x),說明我必須將[po+1,x]中的所有書都入棧,再將x彈出即可(這時還應把po賦值爲x)。否則說明下一個出棧的元素已經在棧中,並且肯定是棧頂元素,若棧頂元素與下一個出棧元素不相等的話,我們可以判斷這不是一個合法出棧序列,否則,若所有的出棧元素都不引起衝突,則說明這是一個合法序列。這裏再說一下時間複雜度,因爲我們只有在下一個出棧元素大於po時,纔將元素壓入棧中,並且我們每一次判斷一個出棧元素是否發生衝突時,都會將棧頂元素彈出,所以每一個元素都入棧一次,出棧一次,所以時間複雜度爲O(n)。
算法的具體實現請看代碼。
- #include <stdio.h>
- #define maxn 1005
- int stack[maxn],top;
- int out[maxn];
- int check(int n)
- {
- int po=0;
- for(int i=1;i<=n;i++)
- {
- for(int j=po+1;j<=out[i];j++)
- {
- po=j;
- stack[top++]=j;
- }
- if(stack[--top]!=out[i])
- return 0;
- }
- return 1;
- }
- int main()
- {
- int n;
- scanf("%d",&n);//假設入棧序列爲1 2。。。。n
- for(int i=1;i<=n;i++)
- {
- scanf("%d",&out[i]);
- }
- if(check(n))
- printf("Yes\n");
- else
- printf("No\n");
- return 0;
- }
判斷是否是合法的出棧序列
棧,這個“後進先出(Last In First Out)” 數據結構應該都不陌生。如果 a、b、c 依次入棧,然後出棧,那麼出棧順序是 c、b、a;如果 a 入棧然後出棧、b 入棧出棧、c 入棧出棧,那麼出棧順序是 a、b、c。如果只是強調 a、b、c 的入棧順序,而不強調具體的出棧順序,那麼 cba 和 abc 都可以是出棧順序,acb、bac 和 bca 也都可以,而 cab 是不可以的:因爲 c 首先出棧說明 a b 在棧中,c 出棧後其他出棧順序只能是 ba 而不可能是 ab。
現在給 n 個數或是字母,假定就是 1、2、3、...、n ,已知它們是按照順序入棧的,有幾個問題:
- 給定一個序列,判斷是否可能是一個出棧序列?比如 1 2 3 ... n 肯定可以,n (n-1) ... 3 2 1 也可以,但是 1 4 2 ... 就不可以;
- 合法的出棧序列有多少種 ?
模擬入棧出棧
me 們可以模擬一下入棧出棧操作,如果可以就是 yes,如果不可以就是 no ! 但如何模擬呢 ? 舉個 1 2 3 4 5 的出棧例子 4 5 3 2 1 。me 們建一個輔助棧(最初是空的)再加一個帶入棧元素 in (最初是 1 ),然後看判斷序列 4 5 3 2 1。
- 待入棧元素 in = 1,當前判斷序列元素是 4,1 ≠ 4 那麼, 1 入棧,然後 in = 2;2 ≠ 4 然後 2 入棧,然後 3 入棧;然後 in = 4;
- in = 4,那麼應該是 4 入棧然後出棧,這裏直接可以將當前判斷元素換成下一個也就是 5 ;
- 4 出棧以後, me 們發現棧頂 top 是 3,不匹配 5,這個時候沒法繼續出棧;那麼執行和第一步類似的操作,使用 in 去判斷;
- in = 5 匹配第二個判斷元素,那麼 5 入棧出棧(直接看下一個判斷元素),這個時候棧頂 top = 3,3 可以出棧,然後棧頂是 2 可以出棧,然後是 1 可以出棧;最後判斷序列元素全部判斷完了,那麼說明序列 4 5 3 2 1 是一個合法的出棧序列;
模擬過程基本如上:最初棧爲空,in = 1;然後依次掃描判斷序列元素 e,如果和 in 不同則需要不斷將 in 入棧(因爲當前棧中元素並不匹配 e);如果 in 和 e 相同則直接判斷下一個元素(可以認爲是 e 先入棧然後出棧),這個時候考慮待判定元素 e 是否可以通過出棧匹配,如果可以則出棧,而且是儘可能多的出棧,如果不可以則有通過繼續將 in 元素壓入棧中尋求匹配。如果判定序列的元素都判定過了,那就是 yes;如果麼法出棧,而 in 又麼法繼續入棧(比如 in 已經超過 n 了),那就是 no !
- #include <iostream>
- #include <vector>
- using namespace std;
-
- bool test_ok(int n, vector<int>& olist);
-
- int main(int argc, char *argv[])
- {
- vector<int> olist;
- int n, x, count=0;
- bool ok;
-
- while(1){
- olist.clear();
-
- cin >> n;
- if(!cin)
- break;
-
- for(int i=0; i<n; ++i){
- cin >> x;
- olist.push_back(x);
- }
- ok = test_ok(n, olist);
- if(ok)
- ++count;
- cout << ok << '\n';
- }
- cout << "count : " << count << endl;
-
- return 0;
- }
-
- bool test_ok(int n, vector<int>& olist)
- {
- vector<int> istack;
- int in = 1, top, oindex = 0;
-
- while(1){
- if(oindex >= n) // ok, olist has no element left !
- return true;
- if(in > n)
- return false;
-
- if(in != olist[oindex]){ // push into stack
- istack.push_back(in);
- ++in;
- continue;
- }
- ++in;
- ++oindex;
- while(!istack.empty() && istack.back() == olist[oindex]){ // pop from stack
- istack.pop_back();
- ++oindex;
- }
- }
- }
全排列
上面提的第二個問題還沒有回答,不過如果 me 們已經可以判斷序列了,那麼將所有的序列都判斷一遍然後數數有多少個合法的,不就可以了 ? 那麼 1、2、3、...、n 的所有序列有多少種呢 ? 好吧,這就是一個全排列丫,有木有 !
1、2、3、...、n 的全排列有 n! 種,這個大家都知道的結論就不多說了。問題是,如何生成 n 個數的全排列,這是這裏關係的重點。其實 me 這裏並不關心如何實現,只是想寫個程序生成 n 個數的全排列而已,不錯的是,c++ 標準庫已經提供了類似的函數,very good !
生成 1-n 的全排列
程序掃描一個數字 n,然後生成其所有的全排列,實際就是 n! 個序列,而每個序列以 n 打頭,這樣的好處就是,程序的結果可以直接傳遞給上面的程序使用 !
- #include <iostream>
- #include <vector>
- #include <algorithm>
- using namespace std;
-
- int main(int argc, char *argv[])
- {
- vector<int> vints;
- int n;
- cin >> n;
-
- for(int i=0; i<n; ++i)
- vints.push_back(i+1);
-
- do {
- cout << n;
- for(int i=0; i<n; ++i)
- cout << ' ' << vints[i];
- cout << '\n';
- } while(next_permutation(vints.begin(), vints.end()));
-
- cout << endl;
-
- return 0;
- }