Leetcode面試題 17.13. 恢復空格——綜合題:字典樹+dp+倒序思想

引入

今天終於把面試題 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];
    }
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章