题目 单词接龙
参考了牛客网算法课助教的代码
单词接龙是一个与我们经常玩的成语接龙相类似的游戏,现在我们已知一组单词,且给定一个开头的字母,要求出以这个字母开头的最长的“龙”(每个单词都最多在“龙”中出现两次),在两个单词相连时,其重合部分合为一部分,例如beast和astonish,如果接成一条龙则变为beastonish,另外相邻的两部分不能存在包含关系,例如at和atide间不能相连。
输入描述:
输入的第一行为一个单独的整数n(n ≤ 20)表示单词数,以下n行每行有一个单词,输入的最后一行为一个单个字符,表示“龙”开头的字母。你可以假定以此字母开头的“龙”一定存在.
输出描述:
只需输出以此字母开头的最长的“龙”的长度
示例:
输入:
5
at
touch
cheat
choose
tact
a
输出:
23
说明:
连成的“龙”为atoucheatactactouchoose
解析:
有几个比较坑的地方需要注意
- 每个单词可以出现2次,也就是说备选列表里需要填进2次单词
- 如果两个单词准备a+b这样接龙但后者覆盖前者,就不能算作接龙
解决办法:
- 在读输入的时候每次往备选列表
list
里加入两边该单词 - 在尝试接龙匹配时,我们设定匹配内容为
start
,这是一个字符串,从尾word.length()-1
向前扩展,当然这个扩展并匹配的过程就是一个遍历,而这个start
遍历到下标为1就停止了,防止出现匹配后者覆盖前者的现象。比如aaabbb匹配aaabbb,那么匹配到aabbb就结束了,因为如果覆盖前者就不算是接龙 - 递归过程中需要的参数有:最新可选的单词列表
list
,用于匹配的前者子串start
,目前已经接龙的字符串pathStr
- 对于每次递归过程,
base case
为 用光所有单词,单词列表长度为0,则跳出递归。 - 对于其他情况需要我们用for循环来控制,如果遍历结束则结束
- for循环中就是选择的一个过程,选择之后会得到新的pathStr,保存最大值
- 如果要加上一个单词接龙,则直接通过process函数获取其作为起始单词以及剩余单词列表所能组成的最长接龙结果
拆解:
- 匹配接龙,对于前者单词word,进行
start
字符串选取并递归匹配(比如aaabbb,start就是b bb bbb abbb…
for(int i=word.length()-1;i>0;i--){
//直接进入递归,递归是从单词列表里选择,遍历过程,并选取最大值
//这里的start就是word.substring(i)
res = Math.max(res,process(list,word.substring(i),path);
//注意合起来写的时候list是newList,这样在每次遍历时新建list,就不用在递归之后还原list
}
- 因为每个单词有两次选取机会,但是我们不能在一轮循环中(选择某单词的下一个单词)重复选,所以创建HashSet来避免重复
HashSet<String> set = new HashSet<>();
//开始遍历list里的每个单词选项啦,这里的单词就是下一个选择
for(String word:list){
//可被选择的条件,首先当然要大于start不然path就没有增加,然后是要匹配上start
if(word.length()>start.length()&&word.substring(0,start.length()).equals(start)){
//还没有被重复选过
if(!set.contains(word)){
//进行我们的新建工作
ArrayList<String> newList = new ArrayList<>(list);
newList.remove(word);//将这个单词从可选列表里移除
set.add(word);//将这个单词加入不可重复set
String newPath = "";
if(path.length()==0){
//这是第一个单词
newPath = word;
}else{
newPath = path + word.substring(start.length());//从start下一个开始,即下标直接是start的长度
}
//接1中,从word里选取start并递归
}
}
}
最后整理一下思路:
- 创建单词选择列表
list
- 选择当前单词
word
在word
遍历选取匹配后缀start
- 在新的单词选择列表里选择新单词匹配
start
- 对于
list
中的每个不重复选择,返回匹配后缀的接龙结果最大值(相当于以该单词为起始) - 注意base case是单词被选完,以及用for循环控制遍历list
完整代码:
import java.util.*;
public class Main{
public static void main(String[] args){
//System.out.println(connect("at","touch"));
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
ArrayList<String> list = new ArrayList<>();
for(int i=0;i<n;i++){
String s = sc.next();
list.add(s);
list.add(s);
}
String startStr = sc.next();
System.out.println(process(list,startStr,""));
sc.close();
}
/**
*
* @param list 目前可选单词列表
* @param start 目前作为开头(前一个单词末尾)的字符串
* @param path 目前已经接龙了的字符串结果
* @return
*/
public static int process(ArrayList<String> list, String start, String path){
if(list.size()==0){
return path.length();
}
int res = path.length();
HashSet<String> set = new HashSet<>();
//开始选择
for(String word:list){
if(word.length()>start.length()&&word.substring(0,start.length()).equals(start)){
//如果大于start的长度且0到start已经跟start匹配上,则可以选择(这里将start每次扩展1位)
//如果还没有选取过
if(!set.contains(word)){
set.add(word);
//直接传递新的list和str,之后就不用恢复了
ArrayList<String> newList = new ArrayList<>(list);//将list填充进去
newList.remove(word);//暂时移除,表示已经选取
String newStr = "";//表示暂时的接龙单词组
if(path.length()==0){
newStr = word;//起始单词
} else {
newStr = path+word.substring(start.length());//在末尾接上本单词
}
for(int i=word.length()-1;i>0;i--){//从最末尾开始,到1结束,作为新的start看能不能匹配上
res = Math.max(res,process(newList,word.substring(i),newStr));//对目前选取的word进行截取成为递归时的start
}
}
}
}
return res;
}
}