查看原文
其他

广度优先搜索:单词梯子

码农的荒岛求生 小风算法 2022-09-29

点击“小风算法”,选择“为星标

大家好,我是小风哥,这是LeetCode刷题系列第9篇。

今天的题目是“单词梯子”,Leetcode第127号题目,要求是这样的:给定两个单词beginWord、endWord以及一个单词数组wordList,问我们能不能在wordList中找到从beginWord到endWord的一系列转化,要求每一次转化能改动一个字符,且找出需要转换次数最少的方案,看个示例理解的更清楚些。

给定beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log","cog"],那么一种转化次数最少的方案是:"hit" -> "hot" -> "dot" -> "dog" -> cog",注意除去beginWord 和endWord 其它单词必须出现在wordlist中,且中间的每次转换只能改动一个字符。

想一想该怎样解决这个问题。

这个问题的解决方法其实很直接,这无非就是一个搜索问题

假设beginWord就是状态A,从状态A开始我们可能产出状态B、C、D,然后开枝散叶出去,状态B、C、D又可能会有各自的状态,就像这样:

状态A能产生哪些后续状态取决于当前单词能不能在wordlist中找到转换后的单词,按照刚才给出的示例,beginWord 也就是hit能在wordList 中找到一次转换为["hot"],这样从hit这个状态就能产生一次后续状态“hot"。

你会发现这其实就是一个多叉树,我们需要找到状态为endWord的节点,并且从根节点到该节点的路径最短。

有了这个发现就简单了,树这种结构的搜索方式无非就是两种:广度优先搜索与深度优先搜索

这两种方式都能解决问题,但考虑到这里需要找到最短路径,那么广度优先搜索天然适合解决最短路径问题

因为广度优先搜索其实是一种层序遍历,只要我们能在某一层中首次发现该节点对应的单词等于endWord那么这一定是最短路径。

从上图看,如果节点D是第一个等于endWord的节点,那么路径A->D就是我们要找的答案。

有了这些分析代码就简单了。

我们首先利用map<string, vector<string>> 找出每个单词对应的转换,这样方便我们找到每个单词的下一个转换:

int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
  map<string, vector<string>> dic;
  wordList.push_back(beginWord);
  int wordListLen = wordList.size();

  for (int i = 0; i < wordListLen; i++) {
    for (int j = 0; j < wordListLen; j++) {
      if (is_one_diff(wordList[i], wordList[j])) {
        dic[wordList[i]].push_back(wordList[j]);
      }
    }
  }
  ...
}

其中is_one_diff非常简单,无非就是判断两个字符串是否只相差一个字符:

bool is_one_diff(string& a, string& b) {
  int len = a.length();
  int diff_num = 0;
  for (int i = 0; i < len; i++) {
    if (a[i] != b[i]) ++diff_num;
    if (diff_num > 1) return false;
  }
  return diff_num == 1;
}

最后利用广度优先遍历找到最短的转换,完整代码为:

int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
  map<string, vector<string>> dic;
  wordList.push_back(beginWord);
  int wordListLen = wordList.size();

  for (int i = 0; i < wordListLen; i++) {
    for (int j = 0; j < wordListLen; j++) {
      if (is_one_diff(wordList[i], wordList[j])) {
        dic[wordList[i]].push_back(wordList[j]);
      }
    }
  }
  vector<string> q;
  q.push_back(beginWord);
  int level = 0;
  map<string, bool> used;
  used[beginWord] = true;

  while(!q.empty()) {
    ++level;
    vector<string> tmp_q;
    for (auto& s: q) {
      if (s == endWord) {
        return level;
      }
      for (auto& next_level_s : dic[s]) {
        if (!used[next_level_s]) {
          tmp_q.push_back(next_level_s);
          used[next_level_s] = true;
        }
      }
    }
    swap(q, tmp_q);
  }
  return 0;
}


您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存