前几天又刷到一单词接龙题,不一样的是这次要求的是能接龙的最短字符串长度,所谓能接龙的意思是最少有两个单词能够接龙(即单词一的尾字母与单词二的首字母一样),如果任意两个单词都无法接起来,输出0。这个题用DFS做是比较简单的,另外如果加些剪枝的话在大单词量的时候效率将会提升很多。
剪枝方法:首尾字母相同的单词在最短结果中最多只使用一次,因此对于首尾字母相同的单词,只记录最短单词的长度即可,其它单词可以扔掉。证明:假设最短结果存在两个首尾字母相同的单词 ...a->axb->bioyher->ra->ayyyb->b....那么必然可以把从第一个相同单词后面那个单词开始,到第二个相同单词结束的一段拿掉,变成...a->axb->b....,因此得证。但是存在一种特殊情况,比如说有下面一组单词: a aa word god hello,显然最优结果是a->aa,首尾字母相同的单词出现了两次,所以对于首尾字母相同的单词,需要记录最短的两个单词的长度。
代码如下:
#include <iostream>
#define N 100
#define CHAR_CNT 26
static int minStrLen = INT32_MAX;
static int length[CHAR_CNT][CHAR_CNT];//下标0对应字母a,下标1对应字母b,依此类推,第一维代表首字母,第二维代表尾字母
static int special[CHAR_CNT][2];
int getLen(char *str, char *b, char *e) {
*b = str[0];
int i = 0;
while (str[i] != '\0') {
*e = str[i++];
}
return i;
}
void putWord() {
for (int i = 0; i < N; i++) {
char tmp[20], begin, end;
int len, x, y;
scanf("%s", tmp);
len = getLen(tmp, &begin, &end);
x = begin - 'a';
y = end - 'a';
if (length[x][y] == 0) {
length[x][y] = len;
}
else {
length[x][y] = len < length[x][y] ? len : length[x][y];
}
if (x == y) {//首尾字母相同的要记录最小的前两个单词长度
if (special[x][0] == 0 || special[x][0] > len) {
special[x][1] = special[x][0];
special[x][0] = len;
}
else if (special[x][1] == 0 || special[x][1] > len) {
special[x][1] = len;
}
}
}
}
void dfs(int cur, int strLen, int wordCnt) {
if (strLen >= minStrLen)//不可能比已有答案更优
return;
minStrLen = (strLen < minStrLen && wordCnt > 1) ? strLen : minStrLen;//至少要有两个单词
for (int i = 0; i < CHAR_CNT; i++) {
if (length[cur][i] > 0) {
int tmp = length[cur][i];
length[cur][i] = 0;
dfs(i, strLen + tmp, wordCnt + 1);//当前尾字母作为首字母进行下一层的DFS
length[cur][i] = tmp;
}
}
}
void checkSpecial() {
for (int i = 0; i < CHAR_CNT; i++) {
if (special[i][0] == 0 || special[i][1] == 0)
continue;
if (special[i][0] + special[i][1] < minStrLen)
minStrLen = special[i][0] + special[i][1];
}
}
int main()
{
putWord();
for (int i = 0; i < CHAR_CNT; i++) {//遍历26个字母作为有可能的龙头首字母
dfs(i, 0, 0);
}
checkSpecial();
if(minStrLen==INT32_MAX)
std::cout << 0;
else
std::cout << minStrLen;
return 0;
}