单词接龙题目描述:
字典 wordList 中从单词 beginWord 和 endWord 的 转换序列 是一个按下述规格形成的序列 beginWord -> s1 -> s2 -> ... -> sk:
- 每一对相邻的单词只差一个字母。
- 对于 1 <= i <= k 时,每个 si 都在 wordList 中。注意, beginWord 不需要在 wordList 中。
- sk == endWord
给你两个单词 beginWord 和 endWord 和一个字典 wordList ,返回 从 beginWord 到 endWord 的 最短转换序列 中的 单词数目 。如果不存在这样的转换序列,返回 0 。
示例一:
输入:beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log","cog"]
输出:5解释:一个最短转换序列是 "hit" -> "hot" -> "dot" -> "dog" -> "cog", 返回它的长度 5。
如图所示:图取自LeedCode题解中大佬的介绍,这里使用的是无向图广度优先算法,两个节点单词之间的字符差距为1。
如果一开始就构建图,每一个单词都需要和除它以外的另外的单词进行比较,复杂度是O(N×WordLen),这里 NN 是单词列表的长度;
为此,我们在遍历一开始,把所有的单词列表放进一个哈希表中,然后在遍历的时候构建图,每一次得到在单词列表里可以转换的单词,复杂度是 O(26×WordLen),借助哈希表,找到邻居与 NN 无关;(摘自LeedCode题解)
代码中详细注解。
Java实现:
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.Set;
public class Solution {
public int ladderLength(String beginWord, String endWord, List<String> wordList) {
// 第 1 步:先将 wordList 放到哈希表里,便于判断某个单词是否在 wordList 里
Set<String> wordSet = new HashSet<>(wordList);
// 判断集合是否为空,是否包含endWord
if (wordSet.size() == 0 || !wordSet.contains(endWord)) {
return 0;
}
// 剔除beginWord
wordSet.remove(beginWord);
// 第 2 步:图的广度优先遍历,必须使用队列和表示是否访问过的 visited 哈希表
Queue<String> queue = new LinkedList<>();
queue.offer(beginWord); //将beginWord放入队列
Set<String> visited = new HashSet<>();
visited.add(beginWord); //将beginWord放入已访问集合
// 第 3 步:开始广度优先遍历,包含起点,因此初始化的时候步数为 1
int step = 1;
//每进入一次层数加一
while (!queue.isEmpty()) {
int currentSize = queue.size();
for (int i = 0; i < currentSize; i++) {
// 依次遍历当前队列中的单词
String currentWord = queue.poll();//取出当前队首单词
// 如果 currentWord 能够修改 1 个字符与 endWord 相同,则返回 step + 1
if (changeWordEveryOneLetter(currentWord, endWord, queue, visited, wordSet)) {
return step + 1;
}
}
step++;//每进入while循环一次层数加一
}
//while循环中的return step + 1;没有执行才会到这里,也就是没查到,返回0
return 0;
}
/**
* 尝试对 currentWord 修改每一个字符,看看是不是能与 endWord 匹配
*
* @param currentWord:当前单词
* @param endWord:目标单词
* @param queue:队列
* @param visited:已访问元素
* @param wordSet:单词集
* @return 布尔值
*/
private boolean changeWordEveryOneLetter(String currentWord, String endWord,
Queue<String> queue, Set<String> visited, Set<String> wordSet) {
char[] charArray = currentWord.toCharArray();//将当前单词转换为char数组
for (int i = 0; i < endWord.length(); i++) {
//对endWord的每一个字符
// 先保存,然后恢复
char originChar = charArray[i];
for (char k = 'a'; k <= 'z'; k++) {
if (k == originChar) {
//相同就退出当前for循环,即对这一个字符就不用操作了
continue;
}
//不同时
charArray[i] = k; //当前字符置换为k
String nextWord = String.valueOf(charArray); //置换后的char数组转字符串
if (wordSet.contains(nextWord)) {//首先要判断字符集包含这个改过的单词
if (nextWord.equals(endWord)) {//这时候currentWord 修改一个字符,能与 endWord 匹配
return true; //在main函数中就可以拿到最后结果
}
if (!visited.contains(nextWord)) { //如果不能匹配,且未被访问过
queue.add(nextWord); //加入队列
// 注意:添加到队列以后,必须马上标记为已经访问
visited.add(nextWord);
}//实际上,在调用这个方法的时候,要么返回true,否则实际上在调用时queue和visited都会被更改,
// 这就是为什么每次进入while循环的队列都会不同直至找到最终的结果
}
}
// 恢复
charArray[i] = originChar;
}
return false;
}
}
最小基因变化题目描述:
基因序列可以表示为一条由 8 个字符组成的字符串,其中每个字符都是 'A'、'C'、'G' 和 'T' 之一。
假设我们需要调查从基因序列 start 变为 end 所发生的基因变化。一次基因变化就意味着这个基因序列中的一个字符发生了变化。
- 例如,"AACCGGTT" --> "AACCGGTA" 就是一次基因变化。
另有一个基因库 bank 记录了所有有效的基因变化,只有基因库中的基因才是有效的基因序列。
给你两个基因序列 start 和 end ,以及一个基因库 bank ,请你找出并返回能够使 start 变化为 end 所需的最少变化次数。如果无法完成此基因变化,返回 -1 。
注意:起始基因序列 start 默认是有效的,但是它并不一定会出现在基因库中。
示例一:
输入:start = "AACCGGTT", end = "AACCGGTA", bank = ["AACCGGTA"] 输出:1
示例二:
输入:start = "AACCGGTT", end = "AAACGGTA", bank = ["AACCGGTA","AACCGCTA","AAACGGTA"]
输出:2
这个解法跟单词接龙几乎一模一样。
import java.lang.reflect.Array;
import java.util.*;
public class Solution {
public int minMutation(String start, String end, String[] bank) {
//第一步:数据集
Set<String> bankSet = new HashSet<>();
for(String b: bank) {
bankSet.add(b);
}
if(bankSet.size()==0||!bankSet.contains(end)){
return -1;
}
bankSet.remove(start);
//第二步:队列+已访问集合
Queue<String> queue = new LinkedList<>();
queue.offer(start);
Set<String> visited = new HashSet<>();
visited.add(start);
//第三步:广度优先遍历
int step=0;
while (!queue.isEmpty()){
int curSize = queue.size();
for (int i = 0; i < curSize; i++) {
String cur = queue.poll();
if(ChangeOneCanBeEnd(cur,end,queue,visited,bankSet)){
return step+1;
}
}
step++;
}
return -1;
}
private boolean ChangeOneCanBeEnd(String cur, String end, Queue<String> queue, Set<String> visited, Set<String> bankSet) {
char[] curArray = cur.toCharArray();
char[] chars =new char[] {'A','C','T','G'};
for (int i = 0; i < end.length(); i++) {
char originChar = curArray[i];
for(int j = 0; j<chars.length; j++){
char k = chars[j];
if(k==originChar){
continue;
}
curArray[i]=k;
String next = String.valueOf(curArray);
if(bankSet.contains(next)){
if(next.equals(end)){
return true;
}
if (!visited.contains(next)){
queue.offer(next);
visited.add(next);
}
}
}
curArray[i] = originChar;
}
return false;
}
}