• LeetCode:Word Ladder I II


    其他LeetCode题目欢迎访问:LeetCode结题报告索引

    LeetCode:Word Ladder 

    Given two words (start and end), and a dictionary, find the length of shortest transformation sequence 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"]

    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.

    分析:这种题,肯定是每次改变单词的一个字母,然后逐渐搜索,很多人一开始就想到用dfs,其实像这种求最短路径、树最小深度问题bfs最适合,可以参考我的这篇博客bfs(层序遍历)求二叉树的最小深度。本题bfs要注意的问题:

    • 和当前单词相邻的单词是:对当前单词改变一个字母且在字典中存在的单词
    • 找到一个单词的相邻单词,加入bfs队列后,要从字典中删除,因为不删除的话会造成类似于hog->hot->hog的死循环。而删除对求最短路径没有影响,因为我们第一次找到该单词肯定是最短路径,即使后面其他单词也可能转化得到它,路径肯定不会比当前的路径短(如果要输出所有最短路径,则不能立即从字典中删除,具体见下一题)
    • bfs队列中用NULL来标识层与层的间隔,每次碰到层的结尾,遍历深度+1

    我们利用和求二叉树最小深度层序遍历的方法来进行bfs,代码如下:                                                                                                                              本文地址

     1 class Solution {
     2 public:
     3     int ladderLength(string start, string end, unordered_set<string> &dict) {
     4         // IMPORTANT: Please reset any member data you declared, as
     5         // the same Solution instance will be reused for each test case.
     6         //BFS遍历找到的第一个匹配就是最短转换,空字符串是层与层之间的分隔标志
     7         queue<string> Q;
     8         Q.push(start); Q.push("");
     9         int res = 1;
    10         while(Q.empty() == false)
    11         {
    12             string str = Q.front();
    13             Q.pop();
    14             if(str != "")
    15             {
    16                 int strLen = str.length();
    17                 for(int i = 0; i < strLen; i++)
    18                 {
    19                     char tmp = str[i];
    20                     for(char c = 'a'; c <= 'z'; c++)
    21                     {
    22                         if(c == tmp)continue;
    23                         str[i] = c;
    24                         if(str == end)return res+1;
    25                         if(dict.find(str) != dict.end())
    26                         {
    27                             Q.push(str);
    28                             dict.erase(str);
    29                         }
    30                     }
    31                     str[i] = tmp;
    32                 }
    33             }
    34             else if(Q.empty() == false)
    35             {//到达当前层的结尾,并且不是最后一层的结尾
    36                 res++;
    37                 Q.push("");
    38             }
    39         }
    40         return 0;
    41     }
    42 };

    LeetCode:Word Ladder II

    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)                         
    • 有了上面的前驱路径,可以从目标单词开始递归的构造所有最短路径(代码中的函数 ConstructResult
     1 class Solution {
     2 public:
     3     typedef unordered_set<string>::iterator HashIter;
     4     vector<vector<string>> findLadders(string start, string end, unordered_set<string> &dict) {
     5         // Note: The Solution object is instantiated only once and is reused by each test case.
     6         queue<string> Q;
     7         Q.push(start); Q.push("");
     8         bool hasFound = false;
     9         unordered_map<string,vector<string> >prePath;//前驱路径
    10         unordered_set<string> hashtable;//保证bfs时插入队列的元素不存在重复
    11         while(Q.empty() == false)
    12         {
    13             string str = Q.front(), strCopy = str;
    14             Q.pop();
    15             if(str != "")
    16             {
    17                 int strLen = str.length();
    18                 for(int i = 0; i < strLen; i++)
    19                 {
    20                     char tmp = str[i];
    21                     for(char c = 'a'; c <= 'z'; c++)
    22                     {
    23                         if(c == tmp)continue;
    24                         str[i] = c;
    25                         if(str == end)
    26                         {
    27                             hasFound = true;
    28                             prePath[end].push_back(strCopy);
    29                             //找到了一条最短路径,当前单词的其它转换就没必要
    30                             goto END;
    31                         }
    32                         if(dict.find(str) != dict.end())
    33                         {
    34                             prePath[str].push_back(strCopy);
    35                             //保证bfs时插入队列的元素不存在重复
    36                             if(hashtable.find(str) == hashtable.end())
    37                                 {Q.push(str); hashtable.insert(str);}
    38                         }
    39                     }
    40                     str[i] = tmp;
    41                 }
    42             }
    43             else if(Q.empty() == false)//到当前层的结尾,且不是最后一层的结尾
    44             {
    45                 if(hasFound)break;
    46                 //避免进入死循环,把bfs上一层插入队列的元素从字典中删除
    47                 for(HashIter ite = hashtable.begin(); ite != hashtable.end(); ite++)
    48                     dict.erase(*ite);
    49                 hashtable.clear();
    50                 Q.push("");
    51             }
    52         END: ;
    53         }
    54         vector<vector<string> > res;
    55         if(prePath.find(end) == prePath.end())return res;
    56         vector<string> tmpres;
    57         tmpres.push_back(end);
    58         ConstructResult(prePath, res, tmpres, start, end);
    59         return res;
    60     }
    61     
    62 private:
    63     //从前驱路径中回溯构造path
    64     void ConstructResult(unordered_map<string,vector<string> > &prePath, 
    65         vector<vector<string> > &res, vector<string> &tmpres, 
    66         string &start, string &end)
    67     {
    68         if(start == end)
    69         {
    70             reverse(tmpres.begin(), tmpres.end());
    71             res.push_back(tmpres);
    72             reverse(tmpres.begin(), tmpres.end());
    73             return;
    74         }
    75         vector<string> &pre = prePath[end];
    76         for(int i = 0; i < pre.size(); i++)
    77         {
    78             tmpres.push_back(pre[i]);
    79             ConstructResult(prePath, res, tmpres, start, pre[i]);
    80             tmpres.pop_back();
    81         }
    82         
    83     }
    84 };

    另外这一题如果不用队列来进行bfs,可能会更加方便,使用两个哈希表来模拟队列,这样还可以避免前面提到的同一个元素加入队列多次的问题,具体可以参考这篇博客

    【版权声明】转载请注明出处:http://www.cnblogs.com/TenosDoIt/p/3443512.html

  • 相关阅读:
    已解决:No 'Access-Control-Allow-Origin'跨域问题
    (转 )聊聊GIS中的坐标系|再版
    CentOs如何挂载硬盘(手把手教你 )
    CentOS如何挂载硬盘
    CentOS7 复制文件夹和移动文件夹
    centos中怎么查看一个文件的创建时间
    vue项目报错Trailing spaces not allowed.
    (转)Vue框架Element UI教程-安装环境搭建(一)
    windows 根据端口查看进行PID 并杀掉进程
    完美解决CentOS8 yum安装AppStream报错,更新yum后无法makecache的问题
  • 原文地址:https://www.cnblogs.com/TenosDoIt/p/3443512.html
Copyright © 2020-2023  润新知