zoukankan      html  css  js  c++  java
  • 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;
            }
        };
  • 相关阅读:
    (一)Python入门-6面向对象编程:10特殊方法和运算符重载-特殊属性
    (一)Python入门-6面向对象编程:09多态
    (一)Python入门-6面向对象编程:08多重继承-mro()-super()获得父类的定义
    (一)Python入门-6面向对象编程:07面向对象三大特征(封装、继承、多态)-继承
    (一)Python入门-6面向对象编程:06私有属性和私有方法(实现封装)-@property装饰器-get和set方法-总结
    (一)Python入门-6面向对象编程:05方法没有重载-方法的动态性
    (一)Python入门-6面向对象编程:04__del__方法(析构函数)和垃圾回收机制-__call__方法和可调用对象
    python并发编程
    python学习笔记:第21天 常用内置模块之collections和time
    python学习笔记:第20天 多继承、MRO C3算法
  • 原文地址:https://www.cnblogs.com/yxzfscg/p/4514639.html
Copyright © 2011-2022 走看看