LeetCode: Word Ladder I && II

Given two words (beginWord and endWord), and a dictionary, find the length of shortest transformation sequence from beginWord to endWord, such that:

  1. Only one letter can be changed at a time
  2. Each intermediate word must exist in the dictionary

For example,

Given:
start = "hit"
end = "cog"
dict = ["hot","dot","dog","lot","log"]

As one shortest transformation is "hit" -> "hot" -> "dot" -> "dog" -> "cog",
return its length 5.

Note:

  • Return 0 if there is no such transformation sequence.
  • All words have the same length.
  • All words contain only lowercase alphabetic characters.

思路:开始考虑了下使用递归和从两边夹逼,但是做不先去。实际上就是一个BFS。对于求最小路径,BFS最适合不过了啊!!!

虽然采用BFS,但是仍需要注意一些问题。就是访问之后需不需要删除的问题。

1. 将每个单词看成图的一个节点。
2. 当单词s1改变一个字符可以变成存在于字典的单词s2时,则s1与s2之间有连接。
3. 给定s1和s2,问题I转化成了求在图中从s1->s2的最短路径长度。而问题II转化为了求所有s1->s2的最短路径。

无论是求最短路径长度还是求所有最短路径,都是用BFS。在BFS中有三个关键步骤需要实现:

1. 如何找到与当前节点相邻的所有节点。
这里可以有两个策略:
(1) 遍历整个字典,将其中每个单词与当前单词比较,判断是否只差一个字符。复杂度为:n*w,n为字典中的单词数量,w为单词长度。
(2) 遍历当前单词的每个字符x,将其改变成a~z中除x外的任意一个,形成一个新的单词,在字典中判断是否存在。复杂度为:26*w,w为单词长度。
这里可以和面试官讨论两种策略的取舍。对于通常的英语单词来说,长度大多小于100,而字典中的单词数则往往是成千上万,所以策略2相对较优。

2. 如何标记一个节点已经被访问过,以避免重复访问。
可以将访问过的单词从字典中删除。

class Solution {
public:
    int ladderLength(string start, string end, unordered_set<string> &dict) {
        dict.insert(end);
        queue<pair<string,int>> q;
        q.push(make_pair(start,1));
        dict.erase(start);
        while(!q.empty()) {
            string s = q.front().first;
            int len = q.front().second;
            if(s==end) return len;
            q.pop();
            vector<string> neighbors = findNeighbors(s, dict);
            for(int i=0; i<neighbors.size(); i++) 
                q.push(make_pair(neighbors[i],len+1));
        }
        return 0;
    }
    
    vector<string> findNeighbors(string s, unordered_set<string> &dict) {
        vector<string> ret;
        for(int i=0; i<s.size(); i++) {
            char c = s[i];
            for(int j=0; j<26; j++) {
                if(c=='a'+j) continue;
                s[i] = 'a'+j;
                if(dict.count(s)) {
                    ret.push_back(s);    
                    dict.erase(s);    
                }
            }
            s[i] = c;
        }
        return ret;
    }
};

II 

title:

Given two words (start and end), and a dictionary, find all shortest transformation sequence(s) from start to end, such that:

  1. Only one letter can be changed at a time
  2. Each intermediate word must exist in the dictionary

For example,

Given:
start = "hit"
end = "cog"
dict = ["hot","dot","dog","lot","log"]

Return

  [
    ["hit","hot","dot","dog","cog"],
    ["hit","hot","lot","log","cog"]
  ]

Note:

  • All words have the same length.
  • All words contain only lowercase alphabetic characters.

    分析:本题主要的框架和上一题是一样,但是还要解决两个额外的问题:一、 怎样保证求得所有的最短路径;二、 怎样构造这些路径

    第一问题:

    • 不能像上一题第二点注意那样,找到一个单词相邻的单词后就立马把它从字典里删除,因为当前层还有其他单词可能和该单词是相邻的,这也是一条最短路径,比如hot->hog->dog->dig和hot->dot->dog->dig,找到hog的相邻dog后不能立马删除,因为和hog同一层的单词dot的相邻也是dog,两者均是一条最短路径。但是为了避免进入死循环,再hog、dot这一层的单词便利完成后dog还是得从字典中删除。即等到当前层所有单词遍历完后,和他们相邻且在字典中的单词要从字典中删除。
    • 如果像上面那样没有立马删除相邻单词,就有可能把同一个单词加入bfs队列中,这样就会有很多的重复计算(比如上面例子提到的dog就会被2次加入队列)。因此我们用一个哈希表来保证加入队列中的单词不会重复,哈希表在每一层遍历完清空(代码中hashtable)。
    • 当某一层的某个单词转换可以得到end单词时,表示已经找到一条最短路径,那么该单词的其他转换就可以跳过。并且遍历完这一层以后就可以跳出循环,因为再往下遍历,肯定会超过最短路径长度

    第二个问题:

    • 为了输出最短路径,我们就要在比bfs的过程中保存好前驱节点,比如单词hog通过一次变换可以得到hot,那么hot的前驱节点就包含hog,每个单词的前驱节点有可能不止一个,那么每个单词就需要一个数组来保存前驱节点。为了快速查找因此我们使用哈希表来保存所有单词的前驱路径,哈希表的key是单词,value是单词数组。(代码中的unordered_map<string,vector<string> >prePath)                
      class Solution {
      public:
          
          vector<vector<string>> findLadders(string start, string end, unordered_set<string> &dict) {
              vector<vector<string> > results;
              unordered_map<string, vector<string> > traces;  
              dict.insert(end);
              queue<pair<string,int>> q;
              q.push(make_pair(start,1));
              set<string> level_set;
              level_set.insert(start);
              int level = 0;
              int min_level = INT_MAX;
              while(!q.empty()) {
                  string s = q.front().first;
                  int len = q.front().second;
                  if (len > min_level)
                      break;
                  if (len != level){
                      for (set<string>::iterator iter = level_set.begin(); iter != level_set.end(); iter++){
                          dict.erase(*iter);
                      }
                      level = len;
                      level_set.clear();
                  }
                  if(s==end){
                      min_level = level;
                      vector<string> result;
                      result.push_back(s);
                      buildPathFromTraces(traces,end,start,result,results);
                  }
                  q.pop();
                  vector<string> neighbors = findNeighbors(s, dict,level_set);
                  for(int i=0; i<neighbors.size(); i++){
                      if (level_set.find(neighbors[i]) == level_set.end()){
                          q.push(make_pair(neighbors[i],len+1));
                          
                          level_set.insert(neighbors[i]);
                      }
                      traces[neighbors[i]].push_back(s);
                      
                  }
              }
              return results;
          }
          void buildPathFromTraces(unordered_map<string,vector<string> > &traces, string end, string start, vector<string> &result,
              vector<vector<string> > &results){
              if (end == start){
                  reverse(result.begin(), result.end());
                  results.push_back(result);
                  reverse(result.begin(), result.end());
                  return ;
              }
              vector<string> pre = traces[end];
              for (int i = 0 ; i < pre.size(); i++){
                  result.push_back(pre[i]);
                  buildPathFromTraces(traces,pre[i],start,result,results);
                  result.pop_back();
              }
          }
          vector<string> findNeighbors(string s, unordered_set<string> &dict, set<string> &level_set) {
              vector<string> ret;
              for(int i=0; i<s.size(); i++) {
                  char c = s[i];
                  for(int j=0; j<26; j++) {
                      if(c=='a'+j) continue;
                      s[i] = 'a'+j;
                      if(dict.count(s)) {
                          ret.push_back(s);
                      }
                  }
                  s[i] = c;
              }
              return ret;
          }
      };
原文地址:https://www.cnblogs.com/yxzfscg/p/4514639.html