最小基因变化

187 阅读2分钟

这是我参与「掘金日新计划 · 2 月更文挑战」的第 22 天,点击查看活动详情

问题描述

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

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

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

给你两个基因序列 start 和 end ,以及一个基因库 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'] 组成

思路分析

首先我们先要理解一下题目意思,题目会给我们三个参数,分别是:

  • startGene

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

  • endGene

需要转换的最终基因序列,我们需要将起始基因序列通过基因库进行变化,最终需要变成 endGene。

  • bank

基因库,记录了所有有效的基因变化,变化过程中的基因序列都应该要存在基因库中。

看到这里,我们不难看出这道题可以用递归回溯的方法来进行解题,每次我们可以再基因库中找到和当前基因序列相差一个字符的基因序列,将当前基因变化成找到的基因序列,不对递归回溯,直到找到我们需要的基因序列为止。

  • 递归终止条件
if (str == endStr) {
  res = Math.min(res, step);
}

str 为当前基因序列,endStr 为我们想要变化的目标基因序列,如果当前基因序列已经变成了我们想要的目标基因序列,那么我们就可以终止递归了。

  • 寻找可以变化的基因序列
for (let i = 0; i < bank.length; i++) {
  if (flag[i]) continue;
  let count = 0;
  for (let j = 0; j < str.length; j++) {
    if (str[j] != bank[i][j]) count++;
    if (count > 1) break;
  }
  if (count == 1) {
    flag[i] = true;
    dfs(bank[i], endStr, step + 1);
    flag[i] = false;
  }
}

我们可以直接遍历比较两个基因序列的每一个位置的字符,计算两个基因序列相差的字符数,相差字符为 1 的话则可以进行变化。

  • 回溯
if (count == 1) {
  flag[i] = true;
  dfs(bank[i], endStr, step + 1);
  flag[i] = false;
}

我们可以通过 flag 数组来记录每一个基因序列的使用情况,如果是使用过的基因序列的话,我们应该避免重复变化。

完整 AC 代码如下:

AC 代码

/**
 * @param {string} startGene
 * @param {string} endGene
 * @param {string[]} bank
 * @return {number}
 */
var minMutation = function (startGene, endGene, bank) {
  let res = Infinity;
  const flag = new Array(bank.length).fill(false);
  const dfs = (str, endStr, step) => {
    if (str == endStr) {
      res = Math.min(res, step);
    }
    for (let i = 0; i < bank.length; i++) {
      if (flag[i]) continue;
      let count = 0;
      for (let j = 0; j < str.length; j++) {
        if (str[j] != bank[i][j]) count++;
        if (count > 1) break;
      }
      if (count == 1) {
        flag[i] = true;
        dfs(bank[i], endStr, step + 1);
        flag[i] = false;
      }
    }
  };
  dfs(startGene, endGene, 0);
  return res == Infinity ? -1 : res;
};

说在后面

本人为算法业余爱好者,平时只是随着兴趣偶尔刷刷题,如果上面分享有错误的地方,欢迎指出,感激不尽。