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

http://www.lintcode.com/en/problem/word-ladder-ii/

Discussion


跟Word Ladder问题不同之处在于输出所有可行解。因此在BFS过程中,需要存储中间结果,用的是个unordered_map,key是string代表当前节点, value是一个vector<string>用来记录从哪些节点可以到当前节点。 最后再根据这个map来trace出所有可行解。

class Solution {
public:
    /**
      * @param start, a string
      * @param end, a string
      * @param dict, a set of string
      * @return a list of lists of string
      */

    vector<vector<string>> findLadders(string start, string end, unordered_set<string> &dict) {
        // write your code here
        vector<vector<string>> result;
        unordered_set<string> current, next;
        current.emplace(start);
        unordered_set<string> isVisited;
        unordered_map<string, vector<string> > father;
        bool found = false;
        while(current.empty() == false) {
            for(auto word: current) {//去重
                isVisited.emplace(word);
            }
            for(auto word: current) {
                for(int i=0; i<word.size(); i++) {//扩展
                    string new_word = word;
                    for(char c='a'; c<='z'; c++) {
                        if(c ==  new_word[i])
                            continue;
                        swap(c, new_word[i]);//扩展状态
                        if(new_word == end) {
                            found = true;
                            father[new_word].push_back(word);//记录路径
                            break;
                        }
                        if(dict.find(new_word) != dict.end() && 
                        (isVisited.find(new_word) == isVisited.end())) {
                            //isVisited.emplace(new_word);
                            father[new_word].push_back(word);//记录路径
                            next.emplace(new_word);
                        }
                        swap(c, new_word[i]);//恢复
                    }
                }
                //DON'T BREAK HERE EVEN FOUND IS TRUE; -- FIND ALL SOLUTIONS. SO WE NEED TO CHECK OTHER WORDS IN CURRENT QUEUE.在当前层找到最优解了还不能停,因为当前层可能还有别的node也可以再改个letter就变成end
            }
            if(found == true) break;//这里要break因为在当前层找到解以后就不用再trace下一层了,因为要的是shortest path
            current.clear();
            current.swap(next);//更新current层,开始下次循环
        }
        if(found) {
            vector<string> path;
            genPath(father, path, start, end, result);
        }
        return result;
    }
    void genPath(unordered_map<string, vector<string> > &father, vector<string> &path, 
    string start, string curWord, vector<vector<string>> &result) {
        path.push_back(curWord);
        if(curWord == start) {
            result.push_back(path);
            reverse(result.back().begin(), result.back().end());//结果是反的,所以要逆序
        } else {
            for(int i=0; i<father[curWord].size(); i++) {
                string tmp = father[curWord][i];
                genPath(father, path, start, tmp, result);
            }
        }
        path.pop_back();
    }
};

这个题花了太多时间了,灰常的郁闷……刚开始我只是在Word Ladder的基础上增加map存储中间过程,但是……

  1. current和next用queue是不行的,因为在同一层也有可能出现重复的word,对于Word Ladder问题来说这一点是没关系的因为找到一个解返回level就可以了,而这个是要输出所有解。所以current next用的都是unordered_map,这样在emplace的时候就可以防止重复了。
  2. isVisited的更新也跟Word Ladder不一样,不能在next.emplace的时候update isVisited,而应该在开始遍历current层之前标记该层里面所有的word都是访问过的。这一点让我非常的费解。

下面这个solution就是用queue,死活只有77% test cases通过。。。

class Solution {
public:
    /**
      * @param start, a string
      * @param end, a string
      * @param dict, a set of string
      * @return a list of lists of string
      */
    vector<vector<string>> findLadders(string start, string end, unordered_set<string> &dict) {
    // write your code here
    vector<vector<string>> result;
    queue<string> current, next;
    current.push(start);
    unordered_set<string> isVisited;
    unordered_map<string, vector<string> > father;
    bool found = false;
    while (current.empty() == false) {
        queue<string> cpyCur (current);
        while(cpyCur.empty() == false) {
            string tmp = cpyCur.front();
            cpyCur.pop();
            isVisited.emplace(tmp);
        }
        while (current.empty() == false) {
            string curStr = current.front();
            current.pop();
            for (int i = 0; i<curStr.size(); i++) {
                string newStr = curStr;
                for (char c = 'a'; c <= 'z'; c++) {
                    if (c == newStr[i])
                        continue;
                    swap(c, newStr[i]);
                    if (newStr == end) {
                        found = true;
                        father[newStr].push_back(curStr);
                        //isVisited.emplace(newStr);
                        break;
                    }
                    if (dict.find(newStr) != dict.end() && isVisited.find(newStr) == isVisited.end()) {
                        //isVisited.emplace(newStr);
                        father[newStr].push_back(curStr);
                        next.push(newStr);
                    }
                    swap(c, newStr[i]);
                }
            }
            //DON'T BREAK HERE EVEN FOUND IS TRUE; -- FIND ALL SOLUTIONS. SO WE NEED TO CHECK OTHER WORDS IN CURRENT QUEUE.
        }
        if(found == true) break;
        current.swap(next);
    }
    if (found) {
        vector<string> path;
        genPath(father, path, start, end, result);
    }
    return result;
}
    void genPath(unordered_map<string, vector<string> > &father, vector<string> &path, 
    string start, string curWord, vector<vector<string>> &result) {
        path.push_back(curWord);
        if(curWord == start) {
            result.push_back(path);
            reverse(result.back().begin(), result.back().end());
        } else {
            for(int i=0; i<father[curWord].size(); i++) {
                string tmp = father[curWord][i];
                genPath(father, path, start, tmp, result);
            }
        }
        path.pop_back();
    }
};

下面这个方案是在update next的时候update isVisited,也是死活只有77% test cases通过。

class Solution {
public:
    /**
      * @param start, a string
      * @param end, a string
      * @param dict, a set of string
      * @return a list of lists of string
      */

    vector<vector<string>> findLadders(string start, string end, unordered_set<string> &dict) {
        // write your code here
        vector<vector<string>> result;
        unordered_set<string> current, next;
        current.emplace(start);
        unordered_set<string> isVisited;
        unordered_map<string, vector<string> > father;
        bool found = false;
        while(current.empty() == false && found == false) {

            for(auto word: current) {
                for(int i=0; i<word.size(); i++) {
                    string new_word = word;
                    for(char c='a'; c<='z'; c++) {
                        if(c ==  new_word[i])
                            continue;
                        swap(c, new_word[i]);
                        if(new_word == end) {
                            found = true;
                            father[new_word].push_back(word);
                            break;
                        }
                        if(dict.find(new_word) != dict.end() && 
                        (isVisited.find(new_word) == isVisited.end())) {
                            isVisited.emplace(new_word);
                            father[new_word].push_back(word);
                            next.emplace(new_word);
                        }
                        swap(c, new_word[i]);
                    }
                }
                //DON'T BREAK HERE EVEN FOUND IS TRUE; -- FIND ALL SOLUTIONS. SO WE NEED TO CHECK OTHER WORDS IN CURRENT QUEUE.
            }
            //if(found == true) break;
            current.clear();
            current.swap(next);
        }
        if(found) {
            vector<string> path;
            genPath(father, path, start, end, result);
        }
        return result;
    }
    void genPath(unordered_map<string, vector<string> > &father, vector<string> &path, 
    string start, string curWord, vector<vector<string>> &result) {
        path.push_back(curWord);
        if(curWord == start) {
            result.push_back(path);
            reverse(result.back().begin(), result.back().end());
        } else {
            for(int  i=0; i<father[curWord].size(); i++) {
                string tmp = father[curWord][i];
                genPath(father, path, start, tmp, result);
            }
        }
        path.pop_back();
    }
};

第二个实在不科学啊……绝望了……

强迫症又发作了,于是又去leetcode上提交了一下,比lintcode聪明,给出了错误的case:
Input: "red" "tax" ["ted","tex","red","tax","tad","den","rex","pee"]
Output: [["red","ted","tad","tax"],["red","rex","tex","tax"]]
Expected: [["red","ted","tad","tax"],["red","ted","tex","tax"],["red","rex","tex","tax"]]

red刚开始就在dict里,debug发现有可能在中间层又回到了red,因为red刚开始没有被标记为已访问。于是我在current开始之前加上了if (dict.find(start) != dict.end()) { isVisited.emplace(start); }.然并卵。。。这个时候我的内心几乎是崩溃的。。。。

于是我画出来状态转移图,继续debug

red
/ \
ted rex
/ \ /
tad tex
\ /
tax

我发现tex的父亲节点rex是不能在father里面记录下来的!!!!
因为ted-->tex的时候tex已经标记为visited了!!!!!
这就是为什不能在update next的时候更新isVisited,因为这个word还有可能被current层的其它node到达啊!!!你标记了visited,那其它node就到不了这个word了啊!!!!
这一切在Word Ladder问题里面是不存在的因为它不care是通过哪些node到达的tex,只要tex到达了路径长度就加一就完了啊!!!!

让我去哭一会儿……

回来再说一句,BFS里面的去存要非常小心,安全的做法是在产生next之前去存,不是在next产生的时候去存。因为在next产生的时候去存,不能保证current层里每一个node都不受影响。

Update

熟悉了以后再回过头看上面的分析。。。还是要熟!

class Solution {
public:
    /**
      * @param start, a string
      * @param end, a string
      * @param dict, a set of string
      * @return a list of lists of string
      */
    vector<vector<string>> findLadders(string start, string end, unordered_set<string> &dict) {
        unordered_set<string> visited;
        unordered_set<string> level[2];
        unordered_map<string, unordered_set<string> > ancestors;
        int cur = 0;
        level[cur].emplace(start);
        while(level[cur%2].empty() == false) {
            //chech if end has been found, make sure shortest
            if(ancestors.find(end) != ancestors.end()) {
                cur++;
                break;
            }
            //mark current level as visited
            for(auto const& s : level[cur%2]) {
                visited.emplace(s);
            }
            //update ancestor, extend to next level
            level[(cur+1)%2].clear();
            for(auto const& s : level[cur%2]) {
                for(int i=0; i<s.length(); i++) {
                    string tmp = s;
                    for(char c = 'a'; c<='z'; c++) {
                        tmp[i] = c;
                        if(dict.find(tmp) != dict.end() && visited.find(tmp) == visited.end()) {
                            ancestors[tmp].emplace(s);//s is one of tmp's ancestors
                            level[(cur+1)%2].emplace(tmp);
                        }
                    }
                }
            }
            //continue next level
            cur++;
        }
        //generate paths based on ancestors
        vector<vector<string> > result;
        vector<string> path;
        GeneratePaths(start, end, path, ancestors, result);
        return result;
    }

    void GeneratePaths(string start, string current, vector<string> &path, 
    unordered_map<string, unordered_set<string> > &ancestors, 
    vector<vector<string> > &all_paths) {
        //a dfs method trace back from current to start
        path.emplace_back(current);
        if(current == start) {
            all_paths.emplace_back(path);
            reverse(all_paths.back().begin(), all_paths.back().end());//from start to end
        } else {
            //dfs current's ancestors
            for(auto const& s : ancestors[current]) {
                GeneratePaths(start, s, path, ancestors, all_paths);
            }
        }
        path.pop_back();
    }

};

results matching ""

    No results matching ""