程序员面试金典 17.22

本文介绍了一个名为WordTransformer的问题,该问题旨在判断是否能通过一系列的字母替换,将一个单词转换成另一个单词,且转换过程中的所有单词都必须存在于给定的字典中。文章详细解释了如何将此问题转化为图上的路径查找问题,并提供了两种解决方案:深度优先搜索(DFS)和双向广度优先搜索(BFS)。此外,还讨论了优化技巧和测试用例。

Word Transformer:给定两个相同长度的单词,判断是否可以通过一系列的字母代换将一个单词转换为另一个,转换过程中的每个单词都应该出现在给定的字典中。

这是一个在图中找路径的问题,最主要的是如何将输入转换为图,即找到字典中可以互相转换的单词。因为每次只能替换一个单词,所以可以根据每个单词抹掉一个字符后剩余的字符串对原始单词进行分组,这样就找到了可以互相转换的单词。

第一次提交深搜方法超时了,是因为在DFS()返回后又将Visited[adj]设置为了false,这会导致后续搜索过程中再次搜索无效路径,因为已经使用了Found记录是否找到了路径,因此递归返回时,如果找到了路径,Found会跳过后续所有的搜索过程;如果没有找到路径,那么从这个节点再次出发依然是找不到路径,所以可以优化掉。

通过测试用例发现beginWord可以不在wordList中,为了代码写起来方便,需要先preprocess()beginWord也加入到wordList中。

class Solution {
private:
    map<string, size_t> Word2Idx;
    vector<string> Idx2Word;
    vector<vector<bool>> Graph;
    vector<bool> Visited;
    vector<string> Path, Solution;
    bool Found = false;
    void preprocess(const string &beginWord, const vector<string> &wordList)
    {
        bool IsBeginInDict = false;
        for(const string &word : wordList)
        {
            if(beginWord == word) IsBeginInDict = true;
            Idx2Word.push_back(word);
        }
        if(!IsBeginInDict) Idx2Word.push_back(beginWord);
    }
    void createGraph()
    {
        map<string, vector<size_t>> WildCard2Word;
        for(size_t i = 0; i < Idx2Word.size(); i++)
        {
            const string &word = Idx2Word[i];
            Word2Idx[word] = i;
            string WildCard(word);
            for(size_t j = 0; j < word.size(); j++)
            {
                char c = WildCard[j];
                WildCard[j] = ' ';
                WildCard2Word[WildCard].push_back(i);
                WildCard[j] = c;
            }
        }
        Graph.assign(Idx2Word.size(), vector<bool>(Idx2Word.size(), false));
        for(auto iter = WildCard2Word.begin(); iter != WildCard2Word.end(); iter++)
        {
            const vector<size_t> &Adjacent = iter->second;
            for(size_t i = 0; i < Adjacent.size(); i++)
            {
                for(size_t j = i + 1; j < Adjacent.size(); j++)
                {
                    Graph[Adjacent[i]][Adjacent[j]] = Graph[Adjacent[j]][Adjacent[i]] = true;
                }
            }
        }
        Visited.assign(Idx2Word.size(), false);
    }
    void DFS(const size_t beginIdx, const size_t &endIdx)
    {
        if(beginIdx == endIdx){
            Found = true;
            Solution.assign(Path.begin(), Path.end());
            return;
        }
        for(size_t adj = 0; adj < Graph[beginIdx].size(); adj++)
        {
            if(!Found && Graph[beginIdx][adj] && !Visited[adj]){
                Visited[adj] = true;
                Path.push_back(Idx2Word[adj]);
                DFS(adj, endIdx);
                Path.pop_back();
            }
        }
    }
public:
    vector<string> findLadders(string beginWord, string endWord, vector<string>& wordList) {
        preprocess(beginWord, wordList);
        createGraph();
        auto iterBeginWord = Word2Idx.find(beginWord);
        auto iterEndWord = Word2Idx.find(endWord);
        if(iterEndWord == Word2Idx.end()) return Solution;
        Path.push_back(beginWord);
        Visited[iterBeginWord->second] = true;
        DFS(iterBeginWord->second, iterEndWord->second);
        return Solution;
    }
};

一般来说,广搜会比深搜扩展地快一些,同时如果双向广搜的话,就会更快。

预处理的部分和上面是一样的,但是因为有两个队列,所以已访问的节点还需要记录下是从哪个队列扩展出来的,以及广搜不能像深搜一样一直维护结果,所以在扩展的过程中要记录父节点,最后的通过父节点回退到根节点。

class Solution {
private:
    struct BFSNode
    {
        size_t curr, prev;
        bool Forward;
        BFSNode(size_t c = UINT_MAX, size_t p = UINT_MAX, bool b = false) : curr(c), prev(p), Forward(b){}
    };
    map<string, size_t> Word2Idx;
    vector<string> Idx2Word;
    vector<vector<bool>> Graph;
    vector<BFSNode> Visited;
    deque<string> Solution;
    void preprocess(const string &beginWord, const vector<string> &wordList)
    {
        bool IsBeginInDict = false;
        for(const string &word : wordList)
        {
            if(beginWord == word) IsBeginInDict = true;
            Idx2Word.push_back(word);
        }
        if(!IsBeginInDict) Idx2Word.push_back(beginWord);
    }
    void createGraph()
    {
        map<string, vector<size_t>> WildCard2Word;
        for(size_t i = 0; i < Idx2Word.size(); i++)
        {
            const string &word = Idx2Word[i];
            Word2Idx[word] = i;
            string WildCard(word);
            for(size_t j = 0; j < word.size(); j++)
            {
                char c = WildCard[j];
                WildCard[j] = ' ';
                WildCard2Word[WildCard].push_back(i);
                WildCard[j] = c;
            }
        }
        Graph.assign(Idx2Word.size(), vector<bool>(Idx2Word.size(), false));
        for(auto iter = WildCard2Word.begin(); iter != WildCard2Word.end(); iter++)
        {
            const vector<size_t> &Adjacent = iter->second;
            for(size_t i = 0; i < Adjacent.size(); i++)
            {
                for(size_t j = i + 1; j < Adjacent.size(); j++)
                {
                    Graph[Adjacent[i]][Adjacent[j]] = Graph[Adjacent[j]][Adjacent[i]] = true;
                }
            }
        }
        Visited.assign(Idx2Word.size(), BFSNode());
    }
    BFSNode BFS(deque<BFSNode> &FromSource, const deque<BFSNode> &FromDest, bool Forward)
    {
        BFSNode node = FromSource.front();
        FromSource.pop_front();
        const vector<bool> &Adjacent = Graph[node.curr];
        for(size_t adj = 0; adj < Adjacent.size(); adj++)
        {
            if(Adjacent[adj]){
                if(Visited[adj].curr == UINT_MAX){
                    FromSource.push_back(BFSNode(adj, node.curr, Forward));
                    Visited[adj] = FromSource.back();
                }
                else if(Visited[adj].Forward != Forward) return BFSNode(adj, node.curr, Forward);
            }
        }
        return BFSNode();
    }
    void DoubleBFS(const size_t beginIdx, const size_t &endIdx)
    {
        deque<BFSNode> FromSource, FromDest;
        FromSource.push_back(BFSNode(beginIdx, UINT_MAX, true));
        Visited[beginIdx] = FromSource.front();
        FromDest.push_back(BFSNode(endIdx, UINT_MAX, false));
        Visited[endIdx] = FromDest.front();
        while(!FromSource.empty() || !FromDest.empty()){
            BFSNode collision = BFS(FromSource, FromDest, true);
            if(collision.curr != UINT_MAX) return mergePath(collision);
            collision = BFS(FromDest, FromSource, false);
            if(collision.curr != UINT_MAX) return mergePath(collision);
        }
    }
    void mergePath(const BFSNode &collision)
    {
        Solution.push_back(Idx2Word[collision.curr]);
        size_t prev = collision.prev;
        while(prev != UINT_MAX){
            if(collision.Forward) Solution.push_front(Idx2Word[prev]);
            else Solution.push_back(Idx2Word[prev]);
            prev = Visited[prev].prev;
        }
        prev = Visited[collision.curr].prev;
        while(prev != UINT_MAX){
            if(collision.Forward) Solution.push_back(Idx2Word[prev]);
            else Solution.push_front(Idx2Word[prev]);
            prev = Visited[prev].prev;
        }
    }
public:
    vector<string> findLadders(string beginWord, string endWord, vector<string>& wordList) {
        preprocess(beginWord, wordList);
        createGraph();
        auto iterBeginWord = Word2Idx.find(beginWord);
        auto iterEndWord = Word2Idx.find(endWord);
        if(iterEndWord == Word2Idx.end()) return vector<string>();
        DoubleBFS(iterBeginWord->second, iterEndWord->second);
        return vector<string>(Solution.begin(), Solution.end());
    }
};

最后附个测试用例。

"qa"
"sq"
["si","go","se","cm","so","ph","mt","db","mb","sb","kr","ln","tm","le","av","sm","ar","ci","ca","br","ti","ba","to","ra","fa","yo","ow","sn","ya","cr","po","fe","ho","ma","re","or","rn","au","ur","rh","sr","tc","lt","lo","as","fr","nb","yb","if","pb","ge","th","pm","rb","sh","co","ga","li","ha","hz","no","bi","di","hi","qa","pi","os","uh","wm","an","me","mo","na","la","st","er","sc","ne","mn","mi","am","ex","pt","io","be","fm","ta","tb","ni","mr","pa","he","lr","sq","ye"]
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值