1. 问题描述:
一条基因序列由一个带有8个字符的字符串表示,其中每个字符都属于 "A", "C", "G", "T"中的任意一个。假设我们要调查一个基因序列的变化。一次基因变化意味着这个基因序列中的一个字符发生了变化。例如,基因序列由"AACCGGTT" 变化至 "AACCGGTA" 即发生了一次基因变化。与此同时,每一次基因变化的结果,都需要是一个合法的基因串,即该结果属于一个基因库。现在给定3个参数 — start, end, bank,分别代表起始基因序列,目标基因序列及基因库,请找出能够使起始基因序列变化为目标基因序列所需的最少变化次数。如果无法实现目标变化,请返回 -1。
注意:
起始基因序列默认是合法的,但是它并不一定会出现在基因库中。
如果一个起始基因序列需要多次变化,那么它每一次变化之后的基因序列都必须是合法的。
假定起始基因序列与目标基因序列是不一样的。
示例 1:
start: "AACCGGTT"
end: "AACCGGTA"
bank: ["AACCGGTA"]
返回值: 1
示例 2:
start: "AACCGGTT"
end: "AAACGGTA"
bank: ["AACCGGTA", "AACCGCTA", "AAACGGTA"]
返回值: 2
示例 3:
start: "AAAAACCC"
end: "AACCCCCC"
bank: ["AAAACCCC", "AAACCCCC", "AACCCCCC"]
返回值: 3
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/minimum-genetic-mutation
2. 思路分析:
这道题目属于图论思想的问题,我们可以将起始状态与基因库中的所有单词看成是节点,从一个单词能够到达另外一个单词说明对应的这两个单词之间有一条有向边,因为求解的是最少的变化次数,也即求解的是最短路径问题,使用宽搜解决即可。我们可以声明一个哈希表s来记录基因库中的所有单词(可以用来判断是否可以从一个单词合法转换到另外一个单词),哈希表dis来记录到当前单词状态的距离(用来判断之前是否到达过当前的状态并且还可以记录到达当前状态的距离),声明一个双端队列加入起始节点start,在循环中进行宽搜即可,首先弹出队列中的队首节点,对当前弹出的节点对应单词的八个位置尝试将其中一个字符换为"A", "G", "C", "T"中的一个,如果换了之后当前的单词在基因库中存在当前的状态并且之前没有被访问过的话那么就更新到当前状态对应的路径,并且判断当前的状态是否是目标序列,如果满足那么直接返回到达当前状态的距离即可,最先到达目标序列的肯定是最短的。
3. 代码如下:
from typing import List
import collections
class Solution:
def minMutation(self, start: str, end: str, bank: List[str]) -> int:
s = collections.defaultdict(int)
dis = collections.defaultdict(int)
# 最先搜索到的肯定是最短的
dis[start] = 0
chars = ["A", "G", "C", "T"]
queue = collections.deque()
queue.append(start)
# 将单词库中的单词存入到字典中
for x in bank:
s[x] = 1
while queue:
t = queue.popleft()
for i in range(len(t)):
for j in range(4):
# 将当前位置的字符换为AGCT中的一个
t0 = t[0:i] + chars[j] + t[i + 1:]
# 当前的状态在基因库中存在并且当前状态之前是没有到达过的
if s[t0] > 0 and t0 not in dis:
dis[t0] = dis[t] + 1
# 找到目标序列最先找到的肯定路径是最短的
if t0 == end: return dis[t0]
queue.append(t0)
return -1