zoukankan      html  css  js  c++  java
  • [LeetCode#127]Word Ladder

    Problem:

    Given two words (beginWord and endWord), and a dictionary, find the length of shortest transformation sequence from beginWord to endWord, 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"]

    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.

    Analysis:

    This problem is no-trival, but it would help you throughly understand the difference between BFS and DFS.
    Typically, it is easy to come up with the solution of DFS, since once your design the recursive function well, the problem is not hard to implement.
    But for this case, DFS is actually not a good way to solve this problem, it would need to traverse all pathes until we could confidently calculate the shortest path. 
    
    For a problem, If you have to traverse all all possible pathes, you could use DFS. However, if just certain part of the path, you may try to come up with another solution.
    
    Let us analyze the DFS solution at first.

    Solution 1:

    public int ladderLength(String beginWord, String endWord, Set<String> wordDict) {
            String start = beginWord;
            String end = endWord;
            Set<String> dict = wordDict;
            List<List<String>> ret = new ArrayList<List<String>> ();
            if (start == null || end == null || dict == null)
                throw new IllegalArgumentException("The passed in arguments is illegal");
            ArrayList<String> path = new ArrayList<String> ();
            ArrayList<Integer> min = new ArrayList<Integer> ();
            min.add(Integer.MAX_VALUE);
            HashSet<String> visited = new HashSet<String> (); 
            visited.add(start);
            path.add(start);
            findPath(start, end, dict, path, visited, min);
            return min.get(0);
        }
        
        private void findPath(String cur_str, String end, Set<String> dict, ArrayList<String> path, Set<String> visited, ArrayList<Integer> min) {
            if (cur_str.equals(end)) {
                if (path.size() < min.get(0))
                    min.set(0, path.size());
                return;
            }
            for (int pos = 0; pos < end.length(); pos++) {
                char[] cur_array = cur_str.toCharArray();
                for (int i = 0; i < 26; i++) {
                    cur_array[pos] = (char)('a' + i);
                    String new_str = new String(cur_array);
                    if (dict.contains(new_str) && !visited.contains(new_str) && path.size() <= min.get(0) - 1) {
                        visited.add(new_str);
                        path.add(new_str);
                        findPath(new_str, end, dict, path, visited, min);
                        path.remove(path.size()-1);
                        visited.remove(new_str);
                    }
                }
            }
        }

    Improvement Analysis:

    The code structure is easy. But you should make sure that:
    At each word, we have 26 * word_len possible children to follow, rather than 26 charcters to try for next index.
    findPath(new_str, index, end, dict, path, visited, min). 
    The above recursive call format is wrong. When you try to use DFS to solve a problem, we must analyz it clearly in a graph, understand the out degree clearly. (the structure of the graph clearly)
    
    for (int pos = 0; pos < end.length(); pos++) {
        char[] cur_array = cur_str.toCharArray();
        for (int i = 0; i < 26; i++) {
            cur_array[pos] = (char)('a' + i);
            String new_str = new String(cur_array);
           ...
        }
    }
    
    What's more, since this graph include circle(which does not appear in a tree), we must avoid the circle and use a visited array(for a certain path). 
    if (dict.contains(new_str) && !visited.contains(new_str) && path.size() <= min.get(0) - 1) {
        visited.add(new_str);
        path.add(new_str);
        findPath(new_str, end, dict, path, visited, min);
        path.remove(path.size()-1);
        visited.remove(new_str);
    }
    Note: when a search path shorter than the current minimum path, we shoud should cut-off that search branch. 
    if (path.size() <= min.get(0) - 1)
    
    ------------------------------------------------------------------------------------------------------------
    Even the above solution is workable, it is still too complex to solve this problem. Actually, in a graph, BFS search can always find the shortest path from a start point to a target point. (The first time to find the target point, the related path is the shortest path to reach the target point.) 
    Cause the shortest path must be the first valid path, we could tag every new nodes we have encountered during the search process, it could greatly reduce the search space. 
    
    Basic idea:
    We use a queue for the search process, once we found out a new node, rather than search along the the new direction, we just enqueue it into the queue, and dequeue it later. Once we encounter the target word, we return the target word's depth back. The process is just like level-traverse a binary tree. The difference are:
    1. the out-degree of each node is 26*word_len, but most of them would be cut-off through dict.
    2. there could be a circle in the graph, we must use a visited hashset to avoid circle search. 
    
    The idea is simple:
    use cur_num, next_num, and level properly. For this part, we could refer the analysis for level traverse. 
    
    The most dangerous part at this problem is to properly avoid the repeately search during the search process.
    Principle: Once a word was tagged as used, every search path reach it after that time could be cut-off, since we are only care about the shortest depth. 
    A error:
    ------------------------------------------------------------
    String cur_word = queue.poll();
    ....
    visisted.add(cur_word);
    if (wordDict.contains(temp) && !visited.contains(temp)) {
            queue.offer(temp);
            next_num++;
    }
    ---------------------------------------------------------------
    This is very typical error in using BFS, tage a word when it was dequeued from queue. It is right, but could exceed time limit.
    Case:
    start: hot
    target: dog
    dict: [hot, hog, hof, dog]
            hot
        hof     hog
    hog             dog
    step 1: "hot" was dequeued, "hot" was tagged as visited
    step 2: "hof" and "hog" were enqueued 
    step 3: "hof" was dequeued, "hof" was tagged,
    step 4, "hog" was enqueued. 
    (repeat happens, "hog" has already been enqueued at step2, but since it has not been dequeued, it was not tagged as "visited")
    
    To avoid this sitution, the guidance is:
    Reference:https://en.wikipedia.org/wiki/Breadth-first_search
    
    This non-recursive implementation is similar to the non-recursive implementation of depth-first search, but differs from it in two ways:
    1. it uses a queue instead of a stack and
    2. it checks whether a vertex has been discovered before enqueueing the vertex rather than delaying this check until the vertex is dequeued from the queue.
    
    The fix method for this problem is:
    visited.add(start);
    ...
    if (wordDict.contains(temp) && !visited.contains(temp)) {
        queue.offer(temp);
        next_num++;
        visited.add(temp);
    }

    Solution 2:

    public class Solution {
        public int ladderLength(String beginWord, String endWord, Set<String> wordDict) {
            if (beginWord == null || endWord == null || wordDict == null || beginWord.length() != endWord.length())
                throw new IllegalArgumentException("The passed in arguments in illegal!");
            Queue<String> queue = new LinkedList<String> ();
            HashSet<String> visited = new HashSet<String> ();
            queue.offer(beginWord);
            visited.add(beginWord);
            int cur_num = 1;
            int next_num = 0;
            int level = 1;
            int word_len = beginWord.length();
            while (!queue.isEmpty()){
                String cur_word = queue.poll();
                cur_num--;
                char[] char_array = cur_word.toCharArray();
                for (int i = 0; i < word_len; i++) {
                    char copy = char_array[i];
                    for (char c = 'a'; c <= 'z'; c++) {
                        char_array[i] = c;
                        String temp = new String(char_array);
                        if (temp.equals(endWord))
                            return level+1;
                        if (wordDict.contains(temp) && !visited.contains(temp)) {
                            queue.offer(temp);
                            next_num++;
                            visited.add(temp);
                        }
                    }
                    char_array[i] = copy;
                }
                if (cur_num == 0) {
                    cur_num = next_num;
                    next_num = 0;
                    level++;
                }
            }
            return 0;
        }
    }
  • 相关阅读:
    hdu 4308(bfs)
    数位dp
    hdu 4548(素数打表)
    不要把时间浪费在QQ上
    用插值方法构造多项式证明中值问题
    《摩诃般若波罗蜜多心经》 玄奘 译
    证明高斯引理
    《摩诃般若波罗蜜多心经》 玄奘 译
    若一整系数$n$次多项式在有理数域可约,则总可以分解成次数小于$n$的两整系数多项式之积.
    用teamviewer控制内网计算机
  • 原文地址:https://www.cnblogs.com/airwindow/p/4770474.html
Copyright © 2011-2022 走看看