LeetCode Word Ladder

127. Word Ladder

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:

  1. Only one letter can be changed at a time.
  2. Each transformed word must exist in the word list.

Note:

  • Return 0 if there is no such transformation sequence.
  • All words have the same length.
  • All words contain only lowercase alphabetic characters.
  • You may assume no duplicates in the word list.
  • You may assume beginWord and endWord are non-empty and are not the same.

Example 1:

Input:
beginWord = "hit",
endWord = "cog",
wordList = ["hot","dot","dog","lot","log","cog"]

Output: 5

Explanation: As one shortest transformation is "hit" -> "hot" -> "dot" -> "dog" -> "cog",
return its length 5.

Example 2:

Input:
beginWord = "hit"
endWord = "cog"
wordList = ["hot","dot","dog","lot","log"]

Output: 0

Explanation: The endWord "cog" is not in wordList, therefore no possibletransformation.

很有意思的一个题。给定两个单词beginWord和endWord,以及一个字典数组,问从beginWord到endWord,最少需要多少次变换,每次只能变换一个字母,而且变换的单词都必须存在于字典数组中。

很明显是一个BFS题,从beginWord开始广搜,直到第一次搜到endWord时停止,此时走过的变换数就是最小变换数。因为广搜会搜索所有的空间,那么第一次到达endWord也就是最早到达的。
当然BFS的题也都能用DFS来做,但是DFS显然会慢,想想便知,DFS必须搜索完一条完整的路径才能知道结果,才能进入下一条路径开始搜索;而BFS是每条路径同步进行,只要有一条路径到达,就结束搜索。
常规的BFS代码如下:

typedef struct Item {
    string cur;
    string used;
    int step;
    Item(string c, string u, int s)
        : cur(c)
        , used(u)
        , step(s){};
};
class Solution {
public:
    bool differOne(const string& s1, const string& s2)
    {
        int cnt = 0;
        for (size_t i = 0; i < s1.size(); ++i) {
            if (s1[i] != s2[i]) {
                ++cnt;
                if (cnt > 1)
                    return false;
            }
        }
        return cnt == 1;
    }
    int ladderLength(string beginWord, string endWord, vector<string>& wordList)
    {
        size_t n = wordList.size();
        queue<Item> q;
        Item it(beginWord, string(n, ‘0’), 1);
        q.push(it);
        while (!q.empty()) {
            Item t = q.front();
            q.pop();
            if (t.cur == endWord)
                return t.step;
            for (size_t i = 0; i < n; ++i) {
                if (t.used[i] == ‘0’&& differOne(t.cur, wordList[i])) {
                    string newUsed = t.used;
                    newUsed[i] = ‘1’;
                    Item tmp(wordList[i], newUsed, t.step + 1);
                    q.push(tmp);
                }
            }
        }
        return 0;
    }
};

本代码很好理解,定义一个结构体Item,cur为当前到达的单词,用一个和wordList大小相同的字符串used表示走到cur时走过的单词,step表示当前走过的步数。很可惜,这个版本的代码提交MLE,超空间了。肯定是因为Item这个结构体太大了,两个string,一个int。

后来想到一个优化的方法,当某条路径path1走到单词cur时,其他路径就没必要再经过cur了,因为一旦经过cur,必定后续步骤和path1后续要走的路径就相同了,造成了重复搜索。所以我们走过每个单词时,可以直接将该单词置空,这样后续就不会再走该路了,同时这样也不需要Item结构体了。新版代码如下:

class Solution {
public:
    bool differOne(const string& s1, const string& s2)
    {
        int cnt = 0;
        for (size_t i = 0; i < s1.size(); ++i) {
            if (s1[i] != s2[i]) {
                ++cnt;
                if (cnt > 1)
                    return false;
            }
        }
        return cnt == 1;
    }
    int ladderLength(string beginWord, string endWord, vector<string>& wordList)
    {
        size_t n = wordList.size();
        queue<string> q;
        q.push(beginWord);
        int step = 1;
        while (!q.empty()) {
            size_t len = q.size();
            for (size_t i = 0; i < len; ++i) {
                string t = q.front();
                q.pop();
                if (t == endWord)
                    return step;
                for (size_t j = 0; j < wordList.size(); ++j) {
                    if (wordList[j] != "" && differOne(t, wordList[j])) {
                        q.push(wordList[j]);
                        wordList[j] = "";
                    }
                }
            }
            ++step;
        }
        return 0;
    }
};

本代码提交AC,用时632MS。

二刷。设置一个visited数组也许是更常规的做法:

class Solution {
public:
	bool IsConnect(const string &a, const string &b) {
		int diff = 0, n = a.size();
		for (int i = 0; i < n; ++i) {
			if (a[i] != b[i]) {
				++diff;
				if (diff > 1)return false;
			}
		}
		return diff == 1;
	}
	int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
		int n = wordList.size();
		if (n == 0)return 0;

		vector<int> visited(n, 0);
		queue<string> q;
		q.push(beginWord);
		int ans = 0;
		while (!q.empty()) {
			++ans;
			int m = q.size();
			for (int i = 0; i < m; ++i) {
				string cur = q.front();
				if (cur == endWord)return ans;
				q.pop();
				for (int j = 0; j < n; ++j) {
					if (visited[j] == 0 && IsConnect(cur, wordList[j])) {
						visited[j] = 1;
						q.push(wordList[j]);
					}
				}
			}
		}
		return 0;
	}
};

本代码提交AC,用时532MS。

Leave a Reply

Your email address will not be published. Required fields are marked *