LeetCode 127. Word Ladder

1. 题目描述

Given two words (beginWord and endWord), and a dictionary’s word list, find the length of shortest transformation sequence from beginWord to endWord, such that:

Only one letter can be changed at a time
Each intermediate word must exist in the word list
For example,

Given:
beginWord = “hit”
endWord = “cog”
wordList = [“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.

2. 解题思路

这道题目可以使用图的观点来看待他, 也就是找一个节点到另一个节点之间的最短路径长度问题, 但是图中, 求解这个问题一般是采用Dijkstra 方法求解的, 而且一般处理的还是无向带权图, 这里就不适用了。所以基本上就剩下两种思路DFS 和 BFS。
我们一开始没有过多的考虑到底是使用 DFS 还是 BFS 的问题, 因为在一般情况下, DFS 和 BFS 都是可以处理这类问题的, 并且由于 DFS 写的比较顺手, 于是华丽丽的掉到坑里面去了。
对于这道题目, 非常适合使用BFS 的搜索框架, 搜索的层数就是 两者之间的距离
而像我们一开始试图采用的 DFS, 需要遍历整个搜索空间, 这个时间的消耗的巨大的。

3. code

这里需要说明的是, 我们这里采用的 BFS, 由于需要统计他的层数, 我们想到的是借助 层次遍历的思想, 利用两个队列的切换, 来实现层数的增加。
在 GetNeighbours 这个函数中, 注释的那部分代码是一定需要添加的, 可以节省很大的搜索量, 如果没有这部分代码, 多个路径会访问相同的节点, 造成重复访问, 最直接的表现就是 TLE了。

class Solution {
public:
    int ladderLength(string beginWord, string endWord, unordered_set<string>& wordList) {
        return Search(beginWord, endWord, wordList);
    }

private:
    int Search(string startWord, string endWord, unordered_set<string> & wordList){
        if (startWord == endWord)
            return 0;

        wordList.insert(endWord);

        vector<queue<string>> myqueue(2, queue<string>());
        int count = 0;
        myqueue[0].push(startWord);
        int index = 0;
        while (!myqueue[0].empty() || !myqueue[1].empty()){
            count++;
            while (!myqueue[index].empty()){
                string front = myqueue[index].front();
                myqueue[index].pop();

                if (front == endWord)
                    return count;

                unordered_set<string> toVisited;
                GetNeighbours(front, wordList, toVisited);

                for (string item : toVisited){
                    myqueue[1 - index].push(item);
                }
            }
            index = 1 - index;
        }

        return 0;
    }

    // 获取相邻节点
    void GetNeighbours(string item, unordered_set<string> & wordList, unordered_set<string> & toVisited){
        wordList.erase(item);
        for (int i = 0; i != item.size(); i++){
            string tmp = item;
            for (int j = 0; j != 26; j++){
                tmp[i] = 'a' + j;

                if (wordList.find(tmp) != wordList.end()){
                    toVisited.insert(tmp);
                    wordList.erase(tmp);     // new improvement 这句话一定要加, 可以节省很大的搜索量
                }
            }
        }
    }
};

4. 大神的代码

4.1 Demo1 BFS

他是使用toVisit 的 size 来标记这一层是否遍历完成的, ie, 在说明标记 TAG1 处,执行完这个for 循环之后, 上一层的节点是被全部访问过了的, 容器中剩余的都是这一层的带访问的节点。

class Solution {
public:
    int ladderLength(string beginWord, string endWord, unordered_set<string>& wordDict) {
        wordDict.insert(endWord);
        queue<string> toVisit;
        addNextWords(beginWord, wordDict, toVisit);
        int dist = 2;
        while (!toVisit.empty()) {
            int num = toVisit.size();
            // 我是说明标记 TAG1
            for (int i = 0; i < num; i++) {
                string word = toVisit.front();
                toVisit.pop();
                if (word == endWord) return dist;
                addNextWords(word, wordDict, toVisit);
            }
            dist++;
        }
    }
private:
    void addNextWords(string word, unordered_set<string>& wordDict, queue<string>& toVisit) {
        wordDict.erase(word);
        for (int p = 0; p < (int)word.length(); p++) {
            char letter = word[p];
            for (int k = 0; k < 26; k++) { 
                word[p] = 'a' + k;
                if (wordDict.find(word) != wordDict.end()) {
                    toVisit.push(word);
                    wordDict.erase(word);
                }
            }
            word[p] = letter;
        } 
    } 
};

4.2 Bi-BFS 算法

由于 BFS 算法搜索到后期, 搜索的量是非常大的, 于是可以考虑使用 双向 BFS 搜索的方法, 每次从小端开始搜索, 这样相比 单纯的BFS 可以节省更多的搜索量

class Solution {
public:
    int ladderLength(string beginWord, string endWord, unordered_set<string>& wordDict) {

        int srcLen = beginWord.size(), destLen = endWord.size(), dictSize = wordDict.size();
        if((srcLen != destLen) || !srcLen || !dictSize) return 0; // abnormal cases, return 0; // abnormal cases, just return 0

        unordered_set<string> unusedWords = wordDict; // words that never visited before
        unordered_set<string> activeWords[3];
        int startSet = 0, endSet = 1, nextSet = 2, curDepth = 2, i;
        char tempC, j;

        activeWords[startSet].insert(beginWord);
        activeWords[endSet].insert(endWord);
        unusedWords.erase(beginWord);
        unusedWords.erase(endWord);

        while(!activeWords[startSet].empty()) 
        { // do BFS on startSet
            for(auto it : activeWords[startSet])
            {
                for(i = 0 ; i<srcLen; ++i)
                {
                    for( tempC = it[i], j='a'; j<='z'; ++j)
                    {
                        if(tempC == j) continue;
                        it[i] = j;  
                        if(activeWords[endSet].count(it)>0)
                            return curDepth ;// if the new word is in the endSet, then we find a path
                        if(unusedWords.count(it))
                        { // otherwise, if it is a new word that has never been visited
                            activeWords[nextSet].insert(it);
                            unusedWords.erase(it); 
                        }
                    }// FOR j
                    it[i] = tempC;
                } // FOR i
            } //FOR it
            ++curDepth;
            swap(startSet, nextSet); // swap the startSet and the nextSet
            if(activeWords[startSet].size() > activeWords[endSet].size()) swap(startSet, endSet); // if needed, switch the search direction 
            activeWords[nextSet].clear();
        }
        return 0;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值