Word Ladder II -- leetcode

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.

基本思路:

要找出变化的最短路径,则需要进行一次广度优先搜索,以求得路径最小长度。

再进行一次深度优先搜索,以记录这些能达到目的地的路径。


从一个节点,找到它的邻接点时,通过将每位字符吕a-z逐一进行变化,然后在字典中查找是否存在。

在第一次广度优先搜索之时,记录下每个节点所在树的层次,即距离起始点的最小长度。

然后在深度优先搜索时,利用上面得到的信息。可以避免死循环,以及比淘汰比更短路径更长的路径。

因为每次找邻接点时,检查它是比本节点更上一层的接点。


深度优先搜索,开始与广度优先执行相反的方向。 即如果广度优先执行从start开始, 则深度从end开始。 即自底向上到顶。

因为达到层次1时,0层肯定就是start,无需再计算。 反之,从顶到底的话,因为底部不一定都是end。

而最好的方式,当是广度优先搜索,从end开始。 而深度则从start开始,这样的好处,是省去了最后路径的一个reverse操作。


下面 代码是BFS从start开始,故在最后路径,需要一个reverse操作。

在代码在 leetcode上实际执行时间为476ms。

class Solution {
public:
    vector<vector<string>> findLadders(string start, string end, unordered_set<string> &dict) {
        vector<vector<string>> ans;
        vector<string> trace;
        unordered_map<string, int> visited;
        if (bfs(start, end, dict, visited))
            dfs(start, end, visited, visited[end], trace, ans);
        
        return ans;
    }
    
    bool bfs(string start, string end, const unordered_set<string> &dict, unordered_map<string, int> &visited) {
        queue<string> q;
        q.push(start);
        int level = 0;
        visited[start] = level;
        bool found = false;
        while (!found && !q.empty()) {
            ++level;
            int count = q.size();
            while (count--) {
                string word = q.front();
                q.pop();
                for (int i=0; i<word.size(); i++) {
                    const char ch = word[i];
                    for (char j='a'; j<='z'; j++) {
                        word[i] = j;
                        if (dict.find(word) != dict.end() && visited.find(word) == visited.end()) {
                            q.push(word);
                            visited[word] = level;
                            if (word == end)
                                found = true;
                        }
                    }
                    word[i] = ch;
                }
            }
        }
        return found;
    }
    
    void dfs(const string &start, string &end, const unordered_map<string, int> &levelmap, int level, vector<string> &path, vector<vector<string>> &ans) {
        auto iter = levelmap.find(end);
        if (iter == levelmap.end() || iter->second != level) return;
        if (level == 1) {
            ans.push_back(path);
            ans.back().push_back(end);
            ans.back().push_back(start);
            reverse(ans.back().begin(), ans.back().end());
            return;
        }
        
        path.push_back(end);
        for (int i=0; i<end.size(); i++) {
            const char ch = end[i];
            for (char j='a'; j<='z'; j++) {
                end[i] = j;
                dfs(start, end, levelmap, level-1, path, ans);
            }
            end[i] = ch;
        }
        path.pop_back();
    }
};



上面在BFS过程中,并未存储邻接点信息。即并未保持,树的父子关系。

在进行DFS过程中,邻接点仍需要进行重新计算。

偿试了一下,在BFS保存邻接点信息。每个结点保存了达到该结点的上一层的所有结点。

发现了BFS写起来要比上面复杂了。不再是那么单纯了: 那个visited,存入时间t和判断时间需要特殊考虑

虽然在visited上下了功夫,保证没有循环回路的产生;但无法避免同一层次结点之间的包含。

故在DFS搜索时,还是需要借助level递减这种方式,剔除,那些走了平行结点的路径。因为这些路径比最短路径要长。

此代码在leetcode上实际执行时间为1160ms。

大大的慢于上面的算法。 

而且空间利用比上面更多。

看来此算法不可取。


class Solution {
public:
    vector<vector<string>> findLadders(string start, string end, unordered_set<string> &dict) {
        vector<vector<string>> ans;
        unordered_map<string, unordered_set<string>> tree;
        vector<string> path;
        int level;
        if (bfs(start, end, dict, tree, level))
            dfs(start, end, tree, path, level, ans);
            
        return ans;
    }
    
    bool bfs(string start, string end, const unordered_set<string> &dict, unordered_map<string, unordered_set<string>> &tree, int &level) {
        unordered_set<string> visited;
        queue<string> q;
        q.push(end);
        bool found = false;
        level = 0;
        while (!found && !q.empty()) {
            int count = q.size();
            ++level;
            while (count--) {
                string word = q.front();
                q.pop();
                if (visited.find(word) != visited.end())
                    continue;
                visited.insert(word);
                const string parent = word;
                for (int i=0; i<word.size(); i++) {
                    const char ch = word[i];
                    for (char j='a'; j<='z'; j++) {
                        word[i] = j;
                        if (dict.find(word) != dict.end() && visited.find(word) == visited.end()) {
                            q.push(word);
                            tree[word].insert(parent);
                            if (word == start)
                                found = true;
                        }
                    }
                    word[i] = ch;
                }
            }
        }
        return found;
    }
    
    void dfs(string start, string end, unordered_map<string, unordered_set<string>> &tree, vector<string> &path, int level, vector<vector<string>> &ans) {
        path.push_back(start);
        if (start == end) {
            ans.push_back(path);
        }
        else if (level) {
            const auto &parents = tree[start];
            for (auto p: parents) {
                dfs(p, end, tree, path, level-1, ans);
            }
        }
        path.pop_back();
    }
};


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值