java solution:BFS 算法
class Solution {
public int minMutation(String startGene, String endGene, String[] bank) {
//首先创建一个集合来存储有效基因串
Set<String> bankSet = new HashSet<>(Arrays.asList(bank));
if(!bankSet.contains(endGene)) return -1;
//初始化BFS所需的
char[] genes = new char[]{'A', 'G', 'T', 'C'};
Queue<String> queue = new LinkedList<>();
queue.offer(startGene);
Set<String> visited = new HashSet<>();
visited.add(startGene);
int steps = 0;
while(!queue.isEmpty()) {
int size = queue.size();
for(int i = 0; i < size; i++) {
String current = queue.poll();
if(current.equals(endGene)) return steps;
//将当前字符串转换为字符数组
char [] currArray = current.toCharArray();
for(int j = 0; j < currArray.length; j++) {
char oldChar = currArray[j];
for(char g : genes) {
if(g == oldChar) continue;
currArray[j] = g;
String mutated = new String(currArray);
if(bankSet.contains(mutated) && !visited.contains(mutated)) {
queue.offer(mutated);
visited.add(mutated);
}
}
currArray[j] = oldChar; // 回溯
}
}
steps++;
}
return -1;
}
}
好的,我们来详细剖析这段 Java 代码,理解它的算法思想和执行流程。这其实是一个经典的 图搜索问题,问题背景是基因变换,但本质和图上的最短路径查找非常类似。
🧠 问题建模(算法思想)
🧬 核心问题描述:
我们从一个 start
基因串(字符串),每次只能变换一个字符(只允许变换为 A
、C
、G
或 T
),且每次变换后的字符串必须在 bank
中出现过,最终希望变换到目标字符串 end
,求最少变换次数。
🗺️ 算法模型(抽象为图)
- 每一个有效基因串是 图中的一个节点。
- 如果两个基因串只差一个字符,且变换后的串在
bank
中出现,那么这两个节点之间就有一条边。 - 要找的就是从
start
到end
的 最短路径长度(经过的边数),这是典型的**BFS(广度优先搜索)**应用场景。
🛠️ Java 代码详细解析
Set<String> bankSet = new HashSet<>(Arrays.asList(bank));
if (!bankSet.contains(end)) return -1;
✅ 初始化阶段:
- 把基因库
bank
转成Set
,加快查询速度(O(1)
)。 - 如果目标基因
end
不在bank
中,那肯定到达不了,直接返回-1
。
char[] genes = new char[]{'A', 'C', 'G', 'T'};
Queue<String> queue = new LinkedList<>();
queue.offer(start);
Set<String> visited = new HashSet<>();
visited.add(start);
int steps = 0;
✅ 准备 BFS 所需的结构:
genes
:4 个可选的字符,便于构造变异。queue
:BFS 队列,初始化时把start
放进去。visited
:避免重复访问,防止死循环。steps
:当前已经变换的次数,代表路径长度。
while (!queue.isEmpty()) {
int size = queue.size(); // 当前层有多少节点
✅ BFS 主体循环:
- 每一轮 BFS 相当于一次“变异”,我们遍历当前层所有节点(当前的字符串),进行下一步扩展。
steps
就是变换次数,每一层就对应一次基因变换。
for (int i = 0; i < size; i++) {
String current = queue.poll();
if (current.equals(end)) return steps;
- 从队列中取出当前处理的字符串
current
。 - 如果已经变异成功(等于
end
),返回当前的变异次数steps
。
char[] currArray = current.toCharArray();
for (int j = 0; j < currArray.length; j++) {
char oldChar = currArray[j];
- 遍历字符串中的每一个字符,尝试每个位置进行变异(一个字符变成另一个)。
for (char g : genes) {
if (g == oldChar) continue;
currArray[j] = g;
String mutated = new String(currArray);
- 尝试把当前字符换成
A/C/G/T
中的其他字符,形成一个新字符串mutated
。
if (bankSet.contains(mutated) && !visited.contains(mutated)) {
queue.offer(mutated);
visited.add(mutated);
}
- 如果这个变异后的字符串合法(在 bankSet 中,且没访问过),就放入队列,作为下一层搜索的起点。
currArray[j] = oldChar; // 回溯,恢复原字符
}
}
steps++; // 这一层结束,步数+1
}
- 每次外层
while
循环结束,表示进行了一次合法变异,steps++
。
return -1; // 如果队列空了还没找到,说明变不了
✅ 算法时间复杂度分析
- 状态数:最多为 bank 中字符串个数(假设为
n
)。 - 每个状态的扩展操作:每个字符最多有 3 种变异方式(A/C/G/T 中除了自己),所以最多
8 × 3 = 24
种尝试。 - 整体复杂度:
O(n × m)
,其中n
是 bank 长度,m
是字符串长度(固定为 8)。
🧪 示例解释
以示例 2 为例:
start = "AACCGGTT"
end = "AAACGGTA"
bank = ["AACCGGTA","AACCGCTA","AAACGGTA"]
BFS 过程:
- Start:
"AACCGGTT"
→"AACCGGTA"
(step 1) "AACCGGTA"
→"AAACGGTA"
(step 2)
结果:最少变换次数是 2。
如果你还想了解:
- 用 DFS 解法解决这个问题?
- 如何构造图再用 Dijkstra 来做?
- 或是如何扩展到通用的字符串变换题(比如 Word Ladder)?
可以告诉我,我们可以进一步延伸~