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

 

你可能感兴趣的:(LeetCode)