单词接龙
题目
问题描述
单词接龙是一个与我们经常玩的成语接龙相类似的游戏,现在我们已知一组单词,且给定一个开头的字母,要求出以这个字母开头的最长的“龙”(每个单词都最多在“龙”中出现两次),在两个单词相连时,其重合部分合为一部分,例如 beast和astonish,如果接成一条龙则变为beastonish,另外相邻的两部分不能存在包含关系,例如at 和 atide 间不能相连。
输入格式
输入的第一行为一个单独的整数n (n<=20)表示单词数,以下n 行每行有一个单词,输入的最后一行为一个单个字符,表示“龙”开头的字母。你可以假定以此字母开头的“龙”一定存在.
输出格式
只需输出以此字母开头的最长的“龙”的长度
样例输入
5
at
touch
cheat
choose
tact
a
样例输入
23
样例说明
连成的“龙”为atoucheatactactouchoose
分析
以样例分析,给定第一个字符是a,所以我们就要遍历单词列表[at,touch,cheat,choose,tact]找出以字符a开头的,找出at,然后又以字符t找到touch,和tact,接下来就会像多叉树一样开始扩展,直到每个单词在龙中出现两次或者最后出现的单词没有子节点(像样例中给出的choose一样,单词表中没有下一个单词能与它相接了),然后找到这个多叉树中,从根节点到每个叶子节点的最长节点字符数,就是本题的解。根据树的特性可知,此题用深度优先搜索来做。
用三个单词来演试一下长度计算,单词列表为[touch,choose,tact],假定给定开头字母为t,如下图所示
具体细节请看代码注释。
代码
/**
*
* @param words 单词数组
* @param firstWord 现在为首的单个字符
* @return 最长龙的数量
*/
public static int getResult(String words[],String firstWord) {
// 定义数组存储每个单词的访问量
int isVisited[] = new int[words.length];
int max=0;
int tmp;
// 循环查找以firstWord开头的单词
for(int i=0;i<words.length;i++) {
if(firstWord.equals(words[i].substring(0, 1))) {
// 找到后给此单词的访问量加1
isVisited[i]++;
// 以此单词开始进行深度搜索,搜得的长度赋给tmp
tmp=dfs(words, isVisited, words[i]);
// 将该单词的访问量还原,对下一个匹配单词进行深度搜索。
isVisited[i]--;
// max中存最大长度
max = tmp>max?tmp:max;
}
}
return max;
}
/**
*
* @param words 单词数组
* @param isVisited 访问量数组
* @param preString 单词入口
* @return 龙的最长长度
* at
touch
cheat
choose
tact 以这几个单词为例解释
*/
public static int dfs(String words[],int isVisited[], String preString) {
// 是否还有后面单词的标志位,如果遍历完单词数组还是人没有找到后继单词则此状态为false,直接输出次单词的长度。比如最后一个接到choose,
// 单词列表中中没有以e、se、ose、oose、hoose为首的单词,所以直接返回choose的长度。
boolean hasWord = false;
// 将找后继的单词从后面开始截,比如 找touch的后继,从单词后面截先解个h,然后遍历单词列表中以h开头的,若没有再截ch,找ch开头的,
// 若还是没找到,至到截到ouch,因为题目要求不能有包含关系,所以不能截到0
for(int j=preString.length()-1;j>0;j--) {
int max=0;
// 是否还需要再截的标志位,因为截的位数越少,所连接的龙就越长,所以只要第一次截出来的单词在单词列表中能匹配到,就不用往后再截。
// 例如,cheat,第一次截t,,遍历发现有t开头的,这个时候我们没有必要继续往后截,因为cheat+touch→cheatouch,长度为两个单词长度减j,j越小,龙越长
boolean preFlag=false;
for(int i=0;i<words.length;i++) {
int tmp=0;
// 首先单词表中的长度要大与截的单词长度,其次判断单词表是否有开头为所截单词的单词,最后还得满足找到的单词的访问量小于2
if(words[i].length()>preString.length()-j&&
preString.substring(j, preString.length()).equals(words[i].substring(0, preString.length()-j))
&&isVisited[i]<2) {
// 如果以上条件满足,则表明已经找到了匹配单词,所以后面不用截了,标志位preFlag变为true
preFlag = true;
// 表示该单词后面还有单词
hasWord = true;
// 将该单词访问量加1
isVisited[i]++;
// 继续搜索后面的单词,并把连接最长的存入max,以touch为例,当截到ch时,遍历单词列表,第一次找到cheat,然后以cheat
// 为下一个单词继续搜,并把长度记录到tmp中与max比较,然后遍历到单词choose发现也可以,然后以choose往下搜,最后将以
// cheat和choose往下搜中长度最长的返回。
tmp = j+dfs(words, isVisited, words[i]);
// 访问量恢复,为下一次搜索做准备
isVisited[i]--;
max = tmp>max?tmp:max;
}
}
// preFlag为true时,表示不用往下截了,返回最大值即可
if(preFlag)
return max;
}
// 如果没进循环,表示找不到他的后继了,直接返回单词长度
if (!hasWord)
return preString.length();
return 0;
}
}