• [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.

    思路转载 http://www.cnblogs.com/TenosDoIt/p/3443512.html

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

    第一问题:

    • 不能像上一题第二点注意那样,找到一个单词相邻的单词后就立马把它从字典里删除,因为当前层还有其他单词可能和该单词是相邻的,这也是一条最短路径,比如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     vector<vector<string> > findLadders(string start, string end, unordered_set<string>& dict)
      4     {
      5         vector<vector<string> > result;
      6 
      7         if(start.empty() || end.empty() )
      8             return result;
      9         if(start.size() != end.size())
     10             return result;
     11 
     12         size_t size =  start.size();
     13 
     14         unordered_set<string> cur, next;//use set to aviod add duplicate element
     15         unordered_map<string, vector<string> > father;
     16     unordered_set<string> visited; // 判重
     17 
     18         cur.insert(start);
     19 
     20         bool found = false;
     21 
     22         while(!cur.empty() && !found)
     23         {
     24         for (const auto& word : cur)
     25             visited.insert(word);
     26             for(unordered_set<string>::iterator it = cur.begin(); it != cur.end(); it++)
     27             {
     28                string curStr= *it;
     29 
     30                //cout << "=========" <<curStr <<"========================" <<endl;
     31                for(int i = 0; i< size; i++)
     32                {
     33                    for(char j = 'a'; j <= 'z'; j++ )
     34                    {
     35                        string nextStr = curStr;
     36                        if(nextStr[i] == j)
     37                            continue;
     38                        nextStr[i] = j;
     39                        if(nextStr.compare(end) == 0)
     40                        {
     41                            //if found, just traverse this layer, not go to next layer
     42                            found = true;
     43                            father[nextStr].push_back(curStr);
     44                        }
     45 
     46 #if 0
     47                        cout << "nextStr = " << nextStr<< endl;
     48                        cout << "nextStr [i] = " << nextStr[i]<< endl;
     49                        cout << "i       = " << i<< endl;
     50                        cout << "j       = " << j<< endl;
     51 #endif
     52                        //if(dict.find(nextStr) != dict.end() && cur.find(nextStr) == cur.end())
     53                        if(dict.find(nextStr) != dict.end() && !visited.count(new_word)) 
     54                            // must add "&& cur.find(nextStr) == cur.end()"
     55                            // to avoid the same level 's elemnt point each other
     56                            // for example hot --> dot
     57                            //             hot --> lot
     58                            // but when you travel to dot, may happen dot --> lot
     59                            // but when you travel to lot, may happen lot --> dot
     60                            // so when add it to next, we must confirm that lot is not in this level
     61                        {
     62                            //cout << "insert	" << nextStr<< "	to next queue" << endl;
     63                            next.insert(nextStr);
     64                            father[nextStr].push_back(curStr);
     65                        }
     66                    }
     67                }
     68             }
     69 
     70             // remove the cur layer's elements  form dict
     71             for(unordered_set<string>::iterator it = cur.begin(); it != cur.end(); it++)
     72             {
     73                string tmp = *it;
     74                 if(dict.find(tmp) != dict.end())
     75                 {
     76                     dict.erase(dict.find(tmp));
     77                     //cout << "erase 	" << tmp <<endl;
     78                 }
     79             }
     80 
     81             cur.clear();
     82             swap(cur, next);
     83         }
     84 
     85         vector<string> path;
     86         for(unordered_map<string, vector<string> >::iterator it = father.begin(); it != father.end(); it++)
     87         {
     88             string str =(*it).first;
     89             vector<string> vect =(*it).second;
     90             //cout << "map key :" << str <<endl;
     91             for(int i = 0; i < vect.size(); i++)
     92             {
     93                 //cout << "	map value:" << vect[i]<<endl;
     94             }
     95         }
     96         genPath(start, end, path, father, result);
     97         return result;
     98     }
     99 
    100     void genPath(string start, string end, vector<string>& path, unordered_map<string, vector<string> >map, vector<vector<string> >& result)
    101     {
    102         path.push_back(end);
    103         if(start == end)
    104         {
    105             reverse(path.begin(), path.end());
    106             result.push_back(path);
    107             //printVector(path);
    108             reverse(path.begin(), path.end());
    109         }
    110         else
    111         {
    112             int size = map[end].size();
    113             for( int i = 0; i < size; i++)
    114             {
    115                 string str = map[end][i];
    116                 genPath(start, str, path, map, result);
    117             }
    118         }
    119         path.pop_back();
    120     }
    121 };

    大数据会超时,

    转一个网上找的能通过的版本,随后在仔细分析差距在哪里吧。。

     1 class Solution {
     2     public:
     3         vector<vector<string> > findLadders(string start, string end,
     4                 const unordered_set<string> &dict) {
     5             unordered_set<string> current, next; // 当前层,下一层,用集合是为了去重
     6             unordered_set<string> visited; // 判重
     7             unordered_map<string, vector<string> > father; //
     8             bool found = false;
     9             auto state_is_target = [&](const string &s) {return s == end;};
    10             auto state_extend = [&](const string &s) {
    11                 unordered_set<string> result;
    12                 for (size_t i = 0; i < s.size(); ++i) {
    13                     string new_word(s);
    14                     for (char c = 'a'; c <= 'z'; c++) {
    15                         if (c == new_word[i]) continue;
    16                         swap(c, new_word[i]);
    17                         if ((dict.count(new_word) > 0|| new_word == end) &&
    18                                 !visited.count(new_word)) {
    19                             result.insert(new_word);
    20                         }
    21                         swap(c, new_word[i]); // 恢复该单词
    22                     }
    23                 }
    24                 return result;
    25             };
    26             current.insert(start);
    27             while (!current.empty() && !found) {
    28                 // 先将本层全部置为已访问,防止同层之间互相指向
    29                 for (const auto& word : current)
    30                     visited.insert(word);
    31                 for (const auto& word : current) {
    32                     const auto new_states = state_extend(word);
    33                     for (const auto &state : new_states) {
    34                         if (state_is_target(state)) found = true;
    35                         next.insert(state);
    36                         father[state].push_back(word);
    37                         // visited.insert(state); // 移动到最上面了
    38                     }
    39                 }
    40                 current.clear();
    41                 swap(current, next);
    42             }
    43             vector<vector<string> > result;
    44             if (found) {
    45                 vector<string> path;
    46                 gen_path(father, path, start, end, result);
    47             }
    48             return result;
    49         }
    50     private:
    51         void gen_path(unordered_map<string, vector<string> > &father,
    52                 vector<string> &path, const string &start, const string &word,
    53                 vector<vector<string> > &result) {
    54             path.push_back(word);
    55             if (word == start) {
    56                 result.push_back(path);
    57                 reverse(result.back().begin(), result.back().end());
    58             } else {
    59                 for (const auto& f : father[word]) {
    60                     gen_path(father, path, start, f, result);
    61                 }
    62             }
    63             path.pop_back();
    64         }
    65 };
  • 相关阅读:
    TV 丽音(NICAM)功能
    TV TimeShift和PVR的区别
    VGA、DVI、HDMI三种视频信号接口
    单词记忆
    gdb调试的基本使用
    Shell中字符串的切割、拼接、比较、替换
    I2C通信基本原理及其实现
    为什么单片机需要时钟系统,时钟信号在单片机中扮演怎样的角色?
    HDMI热插拔检测原理
    HDMI接口之HPD(热拔插)
  • 原文地址:https://www.cnblogs.com/diegodu/p/3821739.html
Copyright © 2020-2023  润新知