引入
今天終於把面試題 17.13. 恢復空格這道放着好久沒做的題給做了,確實做這樣一道題要拐很多彎,需要一定量的積累。
題目就暫時不放出來了,拿到這道題的第一反應,我也確實想到了字典樹(ps:剛纔去翻了翻博客日誌,居然沒有寫過字典樹相關的介紹,一會在下文我會寫上)。
使用字典樹只是題解裏面的一個步驟,看到挨個判斷字符串的每個字符是否在字典裏的時候,我又想到了回溯算法,也就是遍歷的時候,對於每一個字符,去判斷是否能從這個字符開始,單獨形成一個單詞。
簡單的說,就比如"abcd",字典[“ab”,“bc”],在判斷第二個字符b的時候,需要去考慮“ab”的情況和“bc”的情況,如果使用遞歸,就要單獨弄出一個新的遞歸函數來判斷“bc”的情況。
使用遞歸,估計複雜度過不了,應該還要使用一個剪枝。剪枝卻十分麻煩,我有一個比較麻煩的思路,在此不做贅述了。另外,回溯需要傳入的參數也是比較多的,不做推薦。
所以,這道題使用回溯,算是比較麻煩的情況。
俗話說的好,凡是能用回溯的地方,都能用dp,這道題dp很好假設:
dp[i]表示:前i個字符最多有dp[i]個字符沒有被識別出來。
初始化條件也很簡單,dp[0]=0就行了。
不過在考慮dp的時候,怎麼去做狀態轉移方程呢?
一種方法是從j=0開始,判斷j到i的字典有沒有在字典樹裏,但是這種方法和使用Set沒什麼區別。
另一種很好的做法是,字典樹倒序,從最後一個字符往前判斷。這樣只要有不匹配的情況,前面的肯定也是不匹配的了。
接下來就介紹下題解。
字典樹
首先說說字典樹,字典樹其實就是樹,含有26個字母的樹。
樹的節點如下:
class Node {
//字典樹
Node[] dict = new Node[26];
boolean isEnd;
public Node() {
this.isEnd = false;
}
}
可以看出,其實就是個多叉樹,其中加入了個isEnd的判斷,用於表示一個單詞是否到了結尾。
字典樹的初始化方式如下:
//初始化字典樹
Node dummy = new Node();
for (String word : dictionary) {
Node point = dummy;
for (int i = word.length() - 1; i >= 0; i--) {//將單詞倒序放入字典樹
int pos = word.charAt(i) - 'a';
if (point.dict[pos] == null) point.dict[pos] = new Node();
point = point.dict[pos];
//如果是單詞最後一個字符,那麼單詞結束
if (i == 0) point.isEnd = true;
}
}
題解
使用倒序+字典樹的方式會好很多,可以關注一下i和j是如何走的。
代碼如下:
import java.util.*;
public class Solution {
class Node {
//字典樹
Node[] dict = new Node[26];
boolean isEnd;
public Node() {
this.isEnd = false;
}
}
public int respace(String[] dictionary, String sentence) {
//初始化字典樹
Node dummy = new Node();
for (String word : dictionary) {
Node point = dummy;
for (int i = word.length() - 1; i >= 0; i--) {//將單詞倒序放入字典樹
int pos = word.charAt(i) - 'a';
if (point.dict[pos] == null) point.dict[pos] = new Node();
point = point.dict[pos];
if (i == 0) point.isEnd = true;
}
}
//使用動態規劃,dp[i]表示前i個字母,未被識別的有dp[i]個。
int LEN = sentence.length();
int[] dp = new int[LEN + 1];
//初始化dp[0]=0;
for (int i = 0; i < sentence.length(); i++) {
dp[i + 1] = dp[i] + 1;//首先默認在該位置沒有匹配
Node point = dummy;
for (int j = i; j >= 0; j--) {
int pos = sentence.charAt(j) - 'a';
if (point.dict[pos] != null) {
point = point.dict[pos];
if (point.isEnd) {
//如果查找到一個單詞
dp[i + 1] = Math.min(dp[j], dp[i + 1]);
} else {
//沒有查找到單詞,繼續向前找
dp[i + 1] = Math.min(dp[j+1] + (i - j), dp[i + 1]);
}
} else {
//說明查找不到單詞了
dp[i + 1] = Math.min(dp[j+1] + (i - j), dp[i + 1]);
break;
}
}
}
return dp[LEN];
}
}