433.最小基因变化


题目:433.最小基因变化

基因序列可以表示为一条由 8 个字符组成的字符串,其中每个字符都是 'A''C''G''T' 之一。

假设我们需要调查从基因序列 start 变为 end 所发生的基因变化。一次基因变化就意味着这个基因序列中的一个字符发生了变化。

例如,"AACCGGTT" --> "AACCGGTA" 就是一次基因变化。
另有一个基因库 bank 记录了所有有效的基因变化,只有基因库中的基因才是有效的基因序列。(变化后的基因必须位于基因库 bank 中)

给你两个基因序列 startend ,以及一个基因库 bank ,请你找出并返回能够使 start 变化为 end 所需的最少变化次数。如果无法完成此基因变化,返回 -1

注意:起始基因序列 start 默认是有效的,但是它并不一定会出现在基因库中。

  • 示例 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
  • 提示:
start.length == 8
end.length == 8
0 <= bank.length <= 10
bank[i].length == 8
start、end 和 bank[i] 仅由字符 ['A', 'C', 'G', 'T'] 组成

思路

这是一个经典的图搜索问题,可以使用广度优先搜索(BFS)来解决。可以将每个基因序列看作图的一个节点,如果两个基因序列之间只相差一个字符,它们之间可以直接连接。问题就转化为从 start 序列到 end 序列的最短路径问题。

  1. 广度优先搜索(BFS):使用 BFS 找最短路径是一个合适的方法,因为 BFS 的性质决定了当我们第一次到达终点时,路径就是最短的。使用队列存储 BFS 的状态,每个状态由当前基因序列和变化次数组成。使用集合来存储基因库,以便于快速查找某个基因序列是否在基因库中。
  2. 邻接关系:两个基因序列之间如果仅有一个字符不同,就可以认为它们是相邻的。我们可以在 BFS 搜索过程中,每次只改变一个字符,并且检查新生成的序列是否在基因库中。
  3. 剪枝:每访问一个基因序列,我们要将它标记为已访问,以避免重复访问。
  4. 返回结果:如果 BFS 搜索到 end 序列,返回当前的变化次数;如果搜索完成仍未找到,返回 -1
  • 时间复杂度:O(4kn),其中 k 是基因库中基因序列的个数,n 是基因序列的长度(通常为 8)
  • 空间复杂度:O(k),其中 k 是基因库中基因序列的个数

代码

public int minMutation(String start, String end, String[] bank) {
    // 将基因库存入哈希集合,便于快速查找
    Set<String> bankSet = new HashSet<>(Arrays.asList(bank));
    // 如果终点基因不在基因库中,无法完成变化
    if (bank.length == 0 || !bankSet.contains(end)) {
        return -1;
    }
    int result = 0;
    // 初始化 BFS 队列,存放基因序列和对应的变化次数
    Queue<String> queue = new LinkedList<>();
    queue.offer(start);
    // 用于标记已访问的基因序列
    Set<String> visited = new HashSet<>();
    visited.add(start);
    // 基因的四个可能字符
    char[] genes = new char[]{'A', 'C', 'G', 'T'};
    // 开始 BFS
    while (!queue.isEmpty()) {
        int size = queue.size();
        // 遍历当前层次的基因序列
        for (int i = 0; i < size; i++) {
            String originString = queue.poll();
            // 如果当前基因序列是目标序列,返回变化次数
            if (originString.equals(end)) {
                return result;
            }
            // 对当前序列的每一个字符进行变换
            char[] currentArray = originString.toCharArray();
            for (int j = 0; j < originString.length(); j++) {
                char c = currentArray[j];
                // 尝试将当前字符替换为基因字符集合中的字符
                for (char gene : genes) {
                    // 不替换相同的字符
                    if (gene == c) {
                        continue;
                    }
                    // 替换字符
                    currentArray[j] = gene;
                    String newString = new String(currentArray);
                    // 如果新的基因在基因库中,且没有访问过,加入队列
                    if (!visited.contains(newString) && bankSet.contains(newString)) {
                        queue.offer(newString);
                        visited.add(newString);
                    }
                }
                // 恢复原始字符
                currentArray[j] = c;
            }
        }
        // 走完当前层,变化次数加1
        result++;
    }
    // 无法从 start 变到 end
    return -1;
}

文章作者: cxyexe
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 cxyexe !
  目录