1. 程式人生 > >Leetcode 127 Word Ladder I

Leetcode 127 Word Ladder I

這道題目首先想到的是DFS,或曰backtracking,也就是每次都找到一個可能的路徑,最後比較所有路徑中最小的就是題目所求。這樣做顯然需要較多的時間,因為我們遍歷了所有的可能性。那麼,有沒有更加快捷的方案呢?
答案是顯然的,那就是BFS。CareerCup上有這道題目,當時沒有注意總結成這麼抽象的方法,這次一定要好好總結一下。首先,雖然題目中沒有一個“圖”的概念,但是我們可以假想構建一個圖,其中圖中的每個頂點都是我們的元素,點和點是如何聯絡起來的呢?如果一個單詞通過改變一次字母,能夠變成另外一個單詞,我們稱之為1 edit distance 距離(是不是想起了leetcode中edit distance那道題目了?)所以,圖中的所有相鄰元素都是edit distance 距離為1的元素。那麼,我們只需要做BFS,哪裡最先遇到我們的target word,那麼我們的距離就是多少。如果遍歷完所有的元素都沒有找到target word,那麼我們就返回1。
另外一個需要注意的地方就是,如果我們曾經遍歷過某個元素,我會將其從字典中刪除,以防以後再次遍歷到這個元素。這裡有幾種情況:
1.以後再也遍歷不到這個元素,那麼我們刪除它當然沒有任何問題。
2.我們以後會遍歷到該元素,又分為兩種情況:
(1)在本層我們就能遍歷到該元素。也就是說,我們到達這個元素有兩條路徑,而且它們都是最短路徑。
舉一個例子應該比較容易理解:比如hot->hog->dog->dig和hot->dot->dog->dig,那麼在第一次遍歷距離hot為1的元素時,我們找到了hog和dot。對hog遍歷時,我們找到了dog,並且將其從字典中刪除。那麼在遍歷距離dot為1的元素時,我們實際上是找不到dog的,因為已經被刪除了。對於本題來說,是沒有什麼影響的,因為到dog距離都是3,到dig距離都是4。但是後面我們做word ladder 2的時候,如果沒有考慮這個情況,將是非常致命的,因為題目要求輸出最短路徑的所有情況,我們稍後討論相關問題
(2)在更下層我們才能夠遍歷到該元素。比如hot->dot->dog->dig和hot->hat->dat->dag->dog->dig,如果第一次我們找到了dog並且將其刪除,那麼第二次我們實際上是找不到這個元素的。這樣對於本題來說,沒有任何影響。對於word ladder 2來說,因為也是要輸出最短路徑,所以也不會有任何影響。但是倘若我們要輸出從起點到終點的所有路徑,那麼我們就要小心這種情況了。


所以,從這裡我們也能夠得到這樣一個結論:對於題目來說,一定要深刻理解每一步為什麼要這樣做。因為每種方式或多或少都會根據題目的特性做一些優化(比如word ladder I 和word ladder II),不僅僅要知道為什麼要做優化,而且要知道優化的代價是什麼,在什麼情況下適用,什麼情況下不適用。
另外一點就是,每做一道題目都要好好總結一下,看看通過這道題目能夠學會什麼。好的題目,應該是會學會一個更加一般性的方法。現在沒有時間去看CLRS,但是好好總結每一道題目,學會的方法也不會少。



幾個程式中需要注意的細節:
1. ditance變數應該初始化為1。這個其實沒有定數,不過根據題目的要求,比如hot->hog->dog,距離是3,由於我們每次distance只有在變化的時候才能增加(也就是說,我們這個變數實際上反映的是,我們“變化”了多少層),所以應該初始化為1
2.如何初始化一個string。由於queue.front()返回的是元素的引用,因此我們必須拷貝那個變數,所以使用string str(queToPop.front());來初始化,然後將元素pop。
3.C++中,實際上提供了swap函式的模板,不得不說還是很方便的。

BFS method: 616ms to pass large set

class Solution
{
public:
	int ladderLength(string start, string end, unordered_set<string> &dict)
	{
		if (start.size() != end.size())
			return 0;
		if (start.empty() || end.empty())
			return 1;
		if (dict.size() == 0)
			return 0;
		int distance = 1; //!!!
		queue<string> queToPush, queToPop;
		queToPop.push(start);
		while (dict.size() > 0 && !queToPop.empty())
		{
			while (!queToPop.empty())
			{
				string str(queToPop.front()); //!!!how to initialize the str
				queToPop.pop(); //!!! should pop after it is used up
				for (int i = 0; i < str.size(); i++)
				{
					for (char j = 'a'; j <= 'z'; j++)
					{
						if (j == str[i])
							continue;
						char temp = str[i];
						str[i] = j;
						if (str == end)
							return distance + 1; //found it
						if (dict.count(str) > 0) //exists in dict
						{
							queToPush.push(str); //find all the element that is one edit away
							dict.erase(str); //delete corresponding element in dict in case of loop
						}
						str[i] = temp; //
					}
				}
			}
			swap(queToPush, queToPop); //!!! how to use swap
			distance++;
		} //end while
		return 0; //all the dict words are used up and we do not find dest word
	} //end function
};


DFS method, Time Limit Exceed in large set:

class Solution
{
public:
	//when the two words are equal, we also must change 1 character at one time
	int Helper(string current, string dest, unordered_set<string>& dict,
			bool isFirstCall)
	{
		int ret = INT_MAX;
		if (current == dest && !isFirstCall)
			return 1; //the step to get here
		if (dict.empty())
			return INT_MAX; //the dict is used up but we do not get the dest
		for (int i = 0; i < current.size(); i++)
			for (int j = 'a'; j <= 'z'; j++)
			{
				if (j == current[i]) //skip the current character
					continue;
				char temp = current[i];
				current[i] = j;
				if (dict.count(current) > 0) //exist such a word in the dict
				{
					dict.erase(current); //delete such a word
					int rettemp = Helper(current, dest, dict);
					if (rettemp != INT_MAX) //have found dest
					{
						ret = min(rettemp + 1, ret);
					}

					dict.insert(current);
				}
				//pay attention to how to restore the element
				//restore in the corresponding nesting!
				current[i] = temp;

			}
		//ret can be INT_MAX or the distance value
		//if we do not find any 1 edit distance word in dict, return fail    
		return ret;
	}
	
	int ladderLength(string start, string end, unordered_set<string> &dict)
	{
		if (start.size() != end.size())
			return 0;
		if (start.empty() || end.empty() || dict.empty())
			return 0;
		bool isEqual = false;
		if (start == end)
			isEqual = true;
		int temp = Helper(start, end, dict, isEqual);
		if (temp == INT_MAX)
			return 0;
		else
			return temp;
	}
};