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都没有重现出来。。。。。。

原文地址:https://www.cnblogs.com/linyx/p/3706688.html