问题描述:
单词接龙是一种有趣的文字游戏。在这个问题中,我们需要按照特定规则进行单词接龙,并找出最长的单词串。规则如下:
1、接龙的单词首字母必须与前一个单词的尾字母相同。
2、当有多个首字母相同的单词可选时,选择长度最长的单词。如果长度相同,则选择字典序最小的单词。
3、已经使用过的单词不能重复使用。
给定一组全部由小写字母组成的单词数组,以及指定的起始单词,请输出按照上述规则形成的最长单词串。单词串是由多个单词直接拼接而成,中间没有空格。
输入格式
输入包含多行:
-
第一行为一个非负整数 K,表示起始单词在数组中的索引,0≤K<N。
-
第二行为一个非负整数 N,表示单词的总数。
-
接下来的 N 行,每行包含一个单词,表示单词数组中的单词。
输出格式
输出一个字符串,表示最终拼接的最长单词串。
样例输入1
0
6
word
dd
da
dc
dword
d
样例输出1
worddwordda
样例输入2
4
6
word
dd
da
dc
dword
d
样例输出2
dwordda
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.PriorityQueue;
import java.util.Scanner;
public class WordChain {
private static String longestChain = "";
public static String longestWordChain(int K, int N, String[] words) {
if (words == null || K < 0 || K >= N) {
throw new IllegalArgumentException("Invalid input parameters");
}
Arrays.sort(words, Comparator.comparingInt(String::length).thenComparing(String::compareTo));
boolean[] used = new boolean[N];
// 确保K在有效范围内
if (K >= words.length) {
throw new IllegalArgumentException("Index K is out of bounds");
}
dfs(words, K, new StringBuilder(words[K]), used, 0);
return longestChain;
}
private static void dfs(String[] words, int currentIndex, StringBuilder currentChain, boolean[] used, int chainLength) {
// 增加对currentChain为空的处理
if (currentChain.length() == 0) {
dfs(words, currentIndex, currentChain, used, chainLength); // 直接递归,不添加字符
return;
}
if (chainLength > longestChain.length()) {
longestChain = currentChain.toString();
}
char lastChar = currentChain.charAt(currentChain.length() - 1);
// 检查words是否为空或currentIndex是否越界
if (words == null || words.length == 0 || currentIndex >= words.length) {
return;
}
for (int i = currentIndex + 1; i < words.length; i++) {
if (!used[i] && words[i].startsWith(String.valueOf(lastChar))) {
used[i] = true;
// 尝试减少StringBuilder对象的创建
currentChain.append(words[i]); // 直接在当前链上追加
dfs(words, i, currentChain, used, chainLength + words[i].length());
// 回溯时重置状态
currentChain.setLength(chainLength); // 恢复之前的长度
used[i] = false;
}
}
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int K = scanner.nextInt();
int N = scanner.nextInt();
scanner.nextLine(); // 读取换行符
String[] words = new String[N];
for (int i = 0; i < N; i++) {
words[i] = scanner.nextLine();
}
String result = longestWordChain(K, N, words);
System.out.println(result);
scanner.close();
}
}
运行示例解析
输入处理
输入包括三个部分:
- 起始单词的索引
K
。 - 单词列表
words
的大小N
。 - 单词列表
words
本身,包含N
个单词。
在 main
方法中,程序使用 Scanner
类从标准输入读取这些值。
排序
在开始搜索之前,程序首先对单词列表进行排序。排序的依据是单词的长度(升序),如果长度相同,则按照字典序排序。这个排序步骤是为了优化搜索过程,使得在搜索时能够更容易地找到可以通过添加一个字母来形成的新单词。
深度优先搜索(DFS)
排序后,程序使用深度优先搜索(DFS)来寻找最长单词链。搜索从索引 K
对应的单词开始。
- 当前链(
currentChain
):用于存储当前正在构建的单词链。 - 已使用数组(
used
):一个布尔数组,用于标记哪些单词已经被使用过,以避免重复访问。 - 链长度(
chainLength
):当前链的长度(即链中单词的总长度,不是单词的数量)。
在搜索过程中,程序会尝试在当前单词的末尾添加一个字母来形成新的单词。如果新的单词在单词列表中且未被使用过,则将其加入当前链,并继续搜索。如果找到更长的链,则更新最长链(longestChain
)。
回溯
在DFS中,回溯是一个重要的步骤。当无法再在当前链的末尾添加字母形成新单词时,程序需要回溯到上一步,尝试其他可能的路径。为了实现回溯,程序在每次递归调用之前保存当前链和已使用数组的状态,并在递归调用之后恢复这些状态。
输出结果
最终,当DFS搜索完所有可能的路径后,longestChain
将包含最长单词链。程序将这个字符串输出到标准输出。
给定输入的详细处理过程
对于给定的输入:
0
6
word
dd
da
dc
dword
d
- 程序首先读取起始索引
K = 0
和单词数量N = 6
。 - 然后,它读取并存储单词列表
["word", "dd", "da", "dc", "dword", "d"]
。 - 接着,程序对单词列表进行排序,得到
["d", "dd", "da", "dc", "word", "dword"]
。 - 从索引
0
(即单词"word"
)开始,程序使用DFS搜索最长单词链。 - 在搜索过程中,程序会发现可以从
"word"
扩展到"dword"
,因为"dword"
以"word"
结尾并且只多了一个字母。 - 没有其他路径比
"word" -> "dword"
更长,因此这条链是最长的。 - 最终,程序输出最长单词链
"worddword"
。
注意:虽然 "d"
是 "word"
的一个子串,但它不能单独形成一个有效的单词链(因为它不能通过添加一个字母从 "word"
直接得到)。然而,在这个特定的例子中,它并不影响最终的结果,因为最长的链仍然是通过 "word"
到 "dword"
。