题目描述
玛雅人有一种密码,如果字符串中出现连续的2012四个数字就能解开密码。给一个长度为N的字符串,(2=<N<=13)该字符串中只含有0,1,2三种数字,问这个字符串要移位几次才能解开密码,每次只能移动相邻的两个数字。例如02120经过一次移位,可以得到20120,01220,02210,02102,其中20120符合要求,因此输出为1.如果无论移位多少次都解不开密码,输出-1。
输入描述:
输入包含多组测试数据,每组测试数据由两行组成。
第一行为一个整数N,代表字符串的长度(2<=N<=13)。
第二行为一个仅由0、1、2组成的,长度为N的字符串。
输出描述:
对于每组测试数据,若可以解出密码,输出最少的移位次数;否则输出-1。
分析
首先注意到,能解開密碼的充要條件是字符串中包含至少一個0、一個1、兩個2. 其必要性不言自明,其充分性解釋如下:對於字符串中出現的字符,不妨設,其中
表示第一個出現的2和第二個出現的2。在這種約定下,使用冒泡排序算法,從小到大排序,我們就能在字符串的頭部見到“2012”這個序列。眾所周知,冒泡排序每次都只移動兩個相鄰的元素,滿足題目中關於“移動”的要求。
於是,對於一個輸入,我們先用上述條件判斷它是否能夠解開密碼。若能,再分析至少要移動多少次可以解開密碼。後面這一步顯然應當運用廣度優先搜索來解決。
代碼如下:
#include <string>
#include <iostream>
#include <set>
#include <memory>
using namespace std;
bool impossible(string& str) {
int stat[3] = {0};
for (int i = 0; i < str.length(); i++) {
stat[str[i] - '0']++;
}
return stat[0] == 0 || stat[1] == 0 || stat[2] < 2;
}
int main() {
int num;
string str;
while (cin >> num >> str) {
if (impossible(str)) {
cout << -1 << endl;
continue;
}
set<string> status;
status.insert(str);
int c;
for (c = 0; c < 100; c++) {
set<string> status2 = status;
status.clear();
for (int i = 0; i < num - 1; i++) {
for(string s: status2) {
if (s.find("2012") != string::npos)
goto report;
string s2 = s;
swap(s2[i], s2[i + 1]);
status.insert(s2);
}
}
}
report: cout << c << endl;
}
}
與尋常思路不同的是,筆者沒有採用隊列來做廣度優先搜索,而是用了set(集合)這個數據結構,目的是為了去重。筆者每次都清空集合,然後依次將處理過的字符串按依次放回集合,其效果類似隊列,但附加了去重的功效。
如果要用隊列來做這題的話,就應當採用其它去重的方式,例如使用散列表來記錄字符串出現的次數。