zoukankan      html  css  js  c++  java
  • word-ladder总结


    title: word ladder总结
    categories: LeetCode
    tags:

    • 算法
    • LeetCode
      comments: true
      date: 2016-10-16 09:42:30

    题意

    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.

    思路

    因为上一个词到下一个词之间,变化的只是一个字母,因此只需遍历所有字母,进行替换即可;

    利用BFS的思想,将改变后的字符串存进队列中,进行下一次的循环。

    题解

    这个的题解有很多种,但是其实都是使用了最基本的BFS思想;

    交换队列,计算层数

    int ladderLength(string begin, string end, unordered_set<string>& wordList) {
        // 当前层,下一层
        queue<string> current, next;
        // 判重
        unordered_set<string> visited;
        // 层次
        int level = 0;
        bool found = false;
        
        // 有点像内联函数
        auto state_is_target = [&](const string &s) {
            return s == end;
        };
        auto state_extend = [&](const string &s) {
            vector<string> result;
            
            for (size_t i = 0; i < s.size(); ++i) {
                string new_word(s);
                for (char c = 'a'; c <= 'z'; c++) {
                    if (c == new_word[i]) continue;
                    swap(c, new_word[i]);
                    if (wordList.count(new_word) > 0 && !visited.count(new_word)) {
                        result.push_back(new_word);
                        visited.insert(new_word);
                    }
                    swap(c, new_word[i]);
                }
            }
            
            return result;
        };
        
        current.push(begin);
        
        while (!current.empty() && !found) {
            ++level;
            while (!current.empty() && !found) {
                const string str = current.front();
                current.pop();
                
                const auto& new_states = state_extend(str);
                for (const auto& state : new_states) {
                    next.push(state);
                    if (state_is_target(state)) {
                        //找到
                        found = true;
                        break;
                    }
                }
            }
            swap(next, current);
        }
        
        if (found) return level + 1;
        else return 0;
    }
    
    

    总结:

    使用了匿名函数,其中命名的state_is_target,state_extend是函数模版,分别做的操作就是判断字符串是否相等,返回字符串BFS遍历的下一层的数组;

    同时用了两层循环,外循环用于计算层数和交换队列,内循环用于计算找到结尾字符串;

    一个队列

    /**
     *  改变单词中的字母, 查看是否存在
     *
     *  @param word     <#word description#>
     *  @param wordDict <#wordDict description#>
     *  @param toVisit  <#toVisit description#>
     */
    void addNextWords(string word, unordered_set<string>& wordDict, queue<string>& toVisit) {
        // 删除wordDict中的word,因为相同的没有用
        wordDict.erase(word);
        
        for (int i = 0; i < (int)word.length(); i++) {
            // 记录下word的字母
            char letter = word[i];
            // 变换word中每个字母,一个一个字母的查找是否匹配,因为只能替换一个字母
            for (int j = 0; j < 26; j++) {
                word[i] = 'a' + j;
                // 找到后加入访问过的队列,并从wordDict中删除
                if (wordDict.find(word) != wordDict.end()) {
                    toVisit.push(word);
                    wordDict.erase(word);
                }
            }
            // 变换回去
            word[i] = letter;
        }
    }
    /**
     *  使用BFS
     *
     *  @param beginWord <#beginWord description#>
     *  @param endWord   <#endWord description#>
     *  @param wordDict  <#wordDict description#>
     *
     *  @return <#return value description#>
     */
    int ladderLength2(string beginWord, string endWord, unordered_set<string>& wordDict) {
        // 加载最后的单词
        wordDict.insert(endWord);
        queue<string> toVisit;
        addNextWords(beginWord, wordDict, toVisit);
        
        // 从2开始,是因为需要经历开始和结尾的两个字母
        int dist = 2;
        // toVisit中存储的是通过改变一个字母就可以到达wordDict中存在的字符串
        while (!toVisit.empty()) {
            int num = (int)toVisit.size();
            for (int i = 0; i < num; i++) {
                string word = toVisit.front();
                toVisit.pop();
                if (word == endWord) return dist;
                addNextWords(word, wordDict, toVisit);
            }
            dist++;
        }
        
        return 0;
    }
    
    

    总结:

    同样是计算BFS的层数;

    两个指针

    /**
     *  使用两个指针和BFS
     *
     *  @param beginWord <#beginWord description#>
     *  @param endWord   <#endWord description#>
     *  @param wordDict  <#wordDict description#>
     *
     *  @return <#return value description#>
     */
    int ladderLength3(string beginWord, string endWord, unordered_set<string>& wordDict) {
        unordered_set<string> head, tail, *phead, *ptail;
        head.insert(beginWord);
        tail.insert(endWord);
        
        int dist = 2;
        // 将phead指向更小size的unordered_set以便减少运行时间
        while (!head.empty() && !tail.empty()) {
            if (head.size() < tail.size()) {
                phead = &head;
                ptail = &tail;
            }
            else {
                phead = &tail;
                ptail = &head;
            }
            unordered_set<string> temp;
            for (auto iterator = phead->begin(); iterator != phead->end(); iterator++) {
                string word = *iterator;
                wordDict.erase(word);
                
                // 原理和上面一样,更换其中的字符,
                for (int i = 0; i < (int)word.length(); i++) {
                    char letter = word[i];
                    for (int k = 0; k < 26; k++) {
                        word[i] = 'a' + k;
                        if (ptail->find(word) != ptail->end())
                            return dist;
                        if (wordDict.find(word) != wordDict.end()) {
                            temp.insert(word);
                            wordDict.erase(word);
                        }
                    }
                    word[i] = letter;
                }
            }
            dist++;
            swap(*phead, temp);
        }
        return 0;
    }
    
    

    总结:

    两个指针指向了unordered_set的head和tail,其做法和第一种方法是相通的,只不过一个用了unordered_set,同时在循环前面还加上了根据数组大小判断指针指向不同的数组;

    使用类

    class WordNode {
    public:
        string word;
        int numSteps;
    
    public:
        WordNode(string w, int n) {
            this->word = w;
            this->numSteps = n;
        }
    };
    /**
     *  使用类去处理记录数据,和前面的思想是类似的
     *
     *  @param beginWord <#beginWord description#>
     *  @param endWord   <#endWord description#>
     *  @param wordDict  <#wordDict description#>
     *
     *  @return <#return value description#>
     */
    int ladderLength4(string beginWord, string endWord, unordered_set<string>& wordDict) {
        queue<WordNode*> queue;
        queue.push(new WordNode(beginWord, 1));
        
        wordDict.insert(endWord);
        
        while (!queue.empty()) {
            WordNode * top = queue.front();
            queue.pop();
            
            string word = top->word;
            if (word == endWord) {
                return top->numSteps;
            }
            
            for (int i = 0; i < word.length(); i++) {
                char temp = word[i];  //先记录后面还有改回来
                for (char c = 'a'; c <= 'z'; c++) {
                    if (temp != c) {
                        word[i] = c;
                    }
                    
                    if (wordDict.find(word) != wordDict.end()) {
                        queue.push(new WordNode(word, top->numSteps + 1));
                        wordDict.erase(word);
                    }
                    
                }
                word[i] = temp;
            }
        }
        return 0;
    }
    
    

    总结:

    使用类去记录每个字符串的单前遍历的层数,本质还是计算层数,只不过用类去保存,这样的好处就是减少了一些计算代码;

    题意

    Given two words (beginWord and endWord), and a dictionary's word list, find all shortest transformation sequence(s) 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"]
    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.

    思路

    肯定需要两个数组,一个用来存储当前已经存在的字符串,一个用来存放遍历结果的字符串,最后两者进行交换,直到遍历当前数组为空;还有一个重点在于可以利用邻接矩阵的思想,用map存储字符串和数组一一对应关系,最后根据顺序进行输出map即可;

    代码

    vector<string> temp_path;
    vector<vector<string>> result_path;
    
    /**
     *  生成路径(从后往前)
     *
     *  @param unordered_map<string <#unordered_map<string description#>
     *  @param path                 <#path description#>
     *  @param start                <#start description#>
     *  @param end                  <#end description#>
     */
    void generatePath(unordered_map<string, unordered_set<string>>& path, const string& start, const string& end) {
        temp_path.push_back(start);
        
        if (start == end) {
            vector<string> ret = temp_path;
            reverse(ret.begin(), ret.end());
            result_path.push_back(ret);
            return ;
        }
        
        for (auto it = path[start].begin(); it != path[start].end(); it++) {
            generatePath(path, *it, end);
            temp_path.pop_back();
        }
    }
    
    /**
     *
     *
     *  @param beginWord <#beginWord description#>
     *  @param endWord   <#endWord description#>
     *  @param wordList  <#wordList description#>
     *
     *  @return <#return value description#>
     */
    vector<vector<string>> findLadders(string beginWord, string endWord, unordered_set<string> &wordList) {
        temp_path.clear();
        result_path.clear();
        
        unordered_set<string> current_step;
        unordered_set<string> next_step;
        
        unordered_map<string, unordered_set<string>> path;
        
        unordered_set<string> unvisited = wordList; // 记录还没有访问过的结点
        
        if (unvisited.count(beginWord) > 0)
            unvisited.erase(beginWord);
        
        // 未访问过的值增加结尾字符串,开始处加入起点字符串
        unvisited.insert(endWord);
        current_step.insert(beginWord);
        
        while (current_step.count(endWord) == 0 && unvisited.size() > 0) {
            // 遍历当前层的所有元素
            for (auto cur = current_step.begin(); cur != current_step.end(); cur++) {
                string word = *cur;
                // 给字符串中的每个字符都替换字母
                for (int i = 0; i < beginWord.size(); i++) {
                    for (int j = 0; j < 26; j++) {
                        string tmp = word;
                        // 相等情况下
                        if (tmp[i] == 'a' + j) {
                            continue;
                        }
                        
                        tmp[i] = 'a' + j;
                        // 表明字典中存在该字符串,可以到达下一步
                        if (unvisited.count(tmp) > 0) {
                            next_step.insert(tmp);
                            path[tmp].insert(word);
                        }
                    }
                }
            }
            
            // 表明下一层中已经不存在任何没有访问过的
            if (next_step.empty()) {
                break;
            }
            
            // 清除所有没有访问过的值
            for (auto it = next_step.begin(); it != next_step.end(); it++) {
                unvisited.erase(*it);
            }
            
            current_step = next_step;
            next_step.clear();
        }
        
        // 存在结果
        if (current_step.count(endWord) > 0) {
            generatePath(path, endWord, beginWord);
        }
        
        return result_path;
    }
    
    
    /**
     *  建立树
     *
     *  @param forward              <#forward description#>
     *  @param backward             <#backward description#>
     *  @param dict                 <#dict description#>
     *  @param unordered_map<string <#unordered_map<string description#>
     *  @param tree                 <#tree description#>
     *  @param reversed             <#reversed description#>
     *
     *  @return <#return value description#>
     */
    bool buildTree(unordered_set<string> &forward, unordered_set<string> &backward, unordered_set<string> &dict, unordered_map<string, vector<string> > &tree, bool reversed)
    {
        if (forward.empty()) return false;
        // 如果开头的长度比结尾长,则从结尾开始找到开头
        if (forward.size() > backward.size())
            return buildTree(backward, forward, dict, tree, !reversed);
        
        // 存在开头和结尾则删除
        for (auto &word: forward) dict.erase(word);
        for (auto &word: backward) dict.erase(word);
        
        unordered_set<string> nextLevel; // 存储树的下一层
        bool done = false; //是否进一步搜索
        for (auto &it: forward) //从树的当前层进行遍历
        {
            string word = it;
            for (auto &c: word)
            {
                char c0 = c; //存储初始值
                for (c = 'a'; c <= 'z'; ++c)
                {
                    if (c != c0)
                    {
                        // 说明找到最终结果
                        if (backward.count(word))
                        {
                            done = true; //找到
                            !reversed ? tree[it].push_back(word) : tree[word].push_back(it); //keep the tree generation direction consistent;
                        }
                        // 说明还没找到最终结果但是在字典中找到
                        else if (!done && dict.count(word))
                        {
                            nextLevel.insert(word);
                            !reversed ? tree[it].push_back(word) : tree[word].push_back(it);
                        }
                    }
                }
                c = c0; //restore the word;
            }
        }
        return done || buildTree(nextLevel, backward, dict, tree, reversed);
    }
    /**
     *  生成路径
     *
     *  @param beginWord            <#beginWord description#>
     *  @param endWord              <#endWord description#>
     *  @param unordered_map<string <#unordered_map<string description#>
     *  @param tree                 <#tree description#>
     *  @param path                 <#path description#>
     *  @param paths                <#paths description#>
     */
    void getPath(string &beginWord, string &endWord, unordered_map<string, vector<string> > &tree, vector<string> &path, vector<vector<string> > &paths) //using reference can accelerate;
    {
        if (beginWord == endWord) paths.push_back(path); //till the end;
        else
        {
            for (auto &it: tree[beginWord])
            {
                path.push_back(it);
                getPath(it, endWord, tree, path, paths); //DFS retrieving the path;
                path.pop_back();
            }
        }
    }
    
    vector<vector<string>> findLadders2(string beginWord, string endWord, unordered_set<string> &dict) {
        vector<vector<string> > paths;
        vector<string> path(1, beginWord);
        if (beginWord == endWord)
        {
            paths.push_back(path);
            return paths;
        }
        
        // 分别存储开头和结尾
        unordered_set<string> forward, backward;
        forward.insert(beginWord);
        backward.insert(endWord);
        
        unordered_map<string, vector<string> > tree;
        // 用来判断是从开头找到结尾还是从结尾找到开头,因为会从长度短的一方开始进行查找
        bool reversed = false;
        if (buildTree(forward, backward, dict, tree, reversed))
            getPath(beginWord, endWord, tree, path, paths);
        return paths;
    }
    
    bool test() {
        unordered_set<string> words = {"hot","dot","dog","lot","log"};
        string start = "hit", end = "cog";
        
        vector<vector<string>> result = findLadders2(start, end, words);
        
        for (auto i = 0; i < result.size(); ++i) {
            for (auto j = 0; j < result[0].size(); ++j) {
                cout << result[i][j] << "->";
            }
            cout << endl;
        }
        return true;
    }
    
    

    这两种方法其实是一样的;

  • 相关阅读:
    上传项目到github上
    app widget设置bitmap时,无作用
    Android Studio 启动app 白屏
    android sqlite 数据库中使用的类型
    android 解决华为系列手机调试时不能打印Logcat日志信息
    android 自定义滚动条图标
    检测邮箱
    js检测是否存在中文
    表单的checkbox选中和取消
    javascript
  • 原文地址:https://www.cnblogs.com/George1994/p/6399868.html
Copyright © 2011-2022 走看看