zoukankan      html  css  js  c++  java
  • Leetcode | Word Ladder

    Given two words (start and end), and a dictionary, find the length of shortest transformation sequence from start to end, such that:

    Only one letter can be changed at a time
    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.

    拿到题目后的第一想法就是BFS,然后要注意怎么找到它的邻居。假设dict里有m个word,每个word的长度为n,找邻居有三种方式:

    1. 遍历dict里所有的word,判断与当前word相交是不是只是一个字符,那么每次找邻居的时间复杂度为O(nm)。

    2. 预先计算好所有的word之间是不是可以相互转换,O(nm^2),显然不适合;

    3. 遍历当前word的每个位置,把当前位置变成另一个字符,判断新产生的word是不是在dict里,时间复杂度为(26*n);

    综上,选择第3种方式。

    第二个注意点就是BFS的退出条件,如果放在queue的pop之后再判断是否等于end,那么就会额外地多一些开销,最好就是在找邻居的时候,如果遇到了end就直接退出。此时return的就是当前的层数。

    BFS的层数计算的话,我习惯于用一个空串作为哨兵,但是这样循环的判断条件就要变成q.size() > 1.

    1320ms,好慢,还是通过了。

     1 class Solution {
     2 public:
     3     int ladderLength(string start, string end, unordered_set<string> &dict) {
     4         if (dict.empty()) return 0;
     5         if (start.empty() || end.empty()) return 0;
     6         if (start.length() != end.length()) return 0;
     7         dict.insert(end);
     8         
     9         queue<string> q;
    10         q.push(start);
    11         q.push("");
    12         int n = start.length();
    13         
    14         int h = 1;
    15         while (q.size() > 1) {
    16             string tmp = q.front();
    17             q.pop();
    18                 
    19             if (tmp.empty()) { h++; q.push(""); continue;}
    20             
    21             for (int i = 0; i < n; ++i) {
    22                 for (char c = 'a'; c <= 'z'; ++c) {
    23                     string next(tmp);
    24                     next[i] = c;
    25                     if (next == end) return h + 1;
    26                     if (next != tmp && dict.find(next) != dict.end()) {
    27                         q.push(next);
    28                         dict.erase(next);
    29                     }
    30                 }
    31             }
    32         }
    33         
    34         return 0;
    35     }
    36 };

     优化

    1. 将第23行(string next(tmp))提到外循环,可以从1320ms降到900ms。

    2. 将层数的判断用一个endOfLayer来判断,循环条件改回!q.empty(),可以再降到856ms。

    3. 不要判断next != tmp(Line 26),而是用tmp[i]  == c 里 直接continue; 再降到740ms。

    4. 将return h + 1;放在dict.find找到之后再判断; 再降到620ms。

    5. 将dict.find改成dict.count(next) > 0。降到460ms。

    这首题是第一次尝试一步一步地优化,在内循环里面的每一个判断,每一步计算都要仔细考虑,这样才能达到更优。

     1 class Solution {
     2 public:
     3     int ladderLength(string start, string end, unordered_set<string> &dict) {
     4         if (dict.empty()) return 0;
     5         if (start.empty() || end.empty()) return 0;
     6         if (start.length() != end.length()) return 0;
     7         dict.insert(end);
     8         
     9         queue<string> q;
    10         q.push(start);
    11         string endOfLayer = start;
    12         int n = start.length();
    13         
    14         int h = 1;
    15         while (!q.empty()) {
    16             string tmp = q.front();
    17             q.pop();
    18             
    19             for (int i = 0; i < n; ++i) {
    20                 string next(tmp);
    21                 for (char c = 'a'; c <= 'z'; ++c) {
    22                     if (tmp[i] == c) continue;
    23                     next[i] = c;
    24                     
    25                     if (dict.count(next) > 0) {
    26                         if (next == end) return h + 1;
    27                         q.push(next);
    28                         dict.erase(next);
    29                     }
    30                 }
    31             }
    32             
    33             if (endOfLayer == tmp) {
    34                 endOfLayer = q.back();
    35                 h++;
    36             }
    37         }
    38         
    39         return 0;
    40     }
    41 };

    Method II

    从网上看到有人用两个vector来模拟queue的层数变换。

    480ms

     1 class Solution {
     2 public:
     3     int ladderLength(string start, string end, unordered_set<string> &dict) {
     4         if (dict.empty()) return 0;
     5         if (start.empty() || end.empty()) return 0;
     6         if (start.length() != end.length()) return 0;
     7         dict.insert(end);
     8         
     9         vector<unordered_set<string> > layers(2);
    10         int cur = 0, pre = 1;
    11         layers[pre].insert(start);
    12         dict.erase(start);
    13         
    14         int n = start.length();
    15         
    16         int h = 1;
    17         while (!layers[pre].empty()) {
    18             layers[cur].clear();           
    19             for (unordered_set<string>::iterator it = layers[pre].begin(); it != layers[pre].end(); ++it) {
    20                 for (int i = 0; i < n; ++i) {
    21                     string next(*it);
    22                     for (char c = 'a'; c <= 'z'; ++c) {
    23                         if ((*it)[i] == c) continue;
    24                         next[i] = c;
    25                         
    26                         if (dict.count(next) > 0) {
    27                             if (next == end) return h + 1;
    28                             layers[cur].insert(next);
    29                             dict.erase(next);
    30                         }
    31                     }
    32                 }
    33             }
    34             
    35             cur = !cur;
    36             pre = !pre;
    37             h++;
    38         }
    39         
    40         return 0;
    41     }
    42 };

    Bug

    另外,我尝试过queue里面保存的是unorder_set<string>::iterator,在本地主机上测试可以,在OJ上就runtime error了。第二天仔细一想,原来是真有bug。

    见以下代码:

     1 int ladderLength(string start, string end, unordered_set<string> &dict) {
     2         if (dict.empty()) return 0;
     3         if (start.empty() || end.empty()) return 0;
     4         if (start.length() != end.length()) return 0;
     5         dict.insert(start);
     6         dict.insert(end);
     7         
     8         queue<unordered_set<string>::iterator> q;
     9         q.push(dict.find(start));
    10         q.push(dict.end());
    11         int n = start.length();
    12         
    13         int h = 1;
    14         while (q.size() > 1) {
    15             unordered_set<string>::iterator t = q.front();
    16             q.pop();
    17                 
    18             if (t == dict.end()) { h++; q.push(dict.end()); continue;}
    19             string tmp(*t);
    20             dict.erase(t);
    21             
    22             for (int i = 0; i < n; ++i) {
    23                 for (char c = 'a'; c <= 'z'; ++c) {
    24                     string next(tmp);
    25                     next[i] = c;
    26                     if (end == tmp) return h;
    27                     if (next == tmp) continue;
    28                     unordered_set<string>::iterator it = dict.find(next);
    29                     if (it != dict.end()) {
    30                         q.push(it);
    31                     }
    32                 }
    33             }
    34         }
    35         
    36         return 0;
    37     }

    首先用指针的话,那么就只能在pop queue的时候erase了(Line 20)。如果有两个word 'a' 和'b',他们都能一步到达'c',那么第一次的时候,那么'c'就会被插入队列两次。因此,'c'对应的这个指针也就会被erase两次!!!当然就是runtime error了。

    不晓得为什么本地主机这么个bug都没有重现出来。。。。。。

  • 相关阅读:
    session
    CSS3盒子模型
    由“从按下回车到网页显示”粗谈网页优化
    springMVC之拦截器
    设置Webdriver启动chrome为默认用户的配置信息
    [Swift]LeetCode498. 对角线遍历 | Diagonal Traverse
    [Swift]LeetCode497. 非重叠矩形中的随机点 | Random Point in Non-overlapping Rectangles
    [Swift]通天遁地Swift
    [Swift]LeetCode996. 正方形数组的数目 | Number of Squareful Arrays
    [Swift]LeetCode995. K 连续位的最小翻转次数 | Minimum Number of K Consecutive Bit Flips
  • 原文地址:https://www.cnblogs.com/linyx/p/3706688.html
Copyright © 2011-2022 走看看