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;
            }
        };
  • 相关阅读:
    easyexcel: The maximum length of cell contents (text) is 32,767 characters
    分库分表情况下添加索引
    如何保证消息顺序执行(Rabbitmq/kafka)
    MySQL Boolean类型的坑
    Redis居然还有比RDB和AOF更强大的持久化方式?
    ThreadLocal的应用场景和注意事项有哪些?
    spring boot 设置tomcat post参数限制
    并发慎用——System.currentTimeMillis()
    Java多线程中static变量的使用
    临时修改session日期格式冲突问题
  • 原文地址:https://www.cnblogs.com/yxzfscg/p/4514639.html
Copyright © 2011-2022 走看看