題目描述
本題難度:Hard
做題日期:2017年3月26日
本題地址: leetcode.com/problems/wo…
Given a non-empty string s and a dictionary wordDict containing a list of non-empty words, add spaces in s to construct a sentence where each word is a valid dictionary word. You may assume the dictionary does not contain duplicate words.
Return all such possible sentences.
For example, given
s = "catsanddog",
dict = ["cat", "cats", "and", "sand", "dog"].
A solution is ["cats and dog", "cat sand dog"].複製程式碼
分析和解答
這道題是本週關於回溯的 Hard 題。
暴力法
首先能想到的是暴力法,遍歷所有的可能。
以題意中的例子做分析
s = "catsanddog",
dict = ["cat", "cats", "and", "sand", "dog"].
圖一
我們從左到右遍歷 catsanddog:
- 當前子串為 c,不符合要求
- 當前子串為 ca,不符合要求
- 當前子串為 cat,符合要求,接著求 sanddog 子串的結果, 如圖二所示
- 當前子串是 cats, 符合要求...
圖二
步驟 3 ,一直遞迴下去,我們可以得到結果 ["cat sand dog"]
程式碼如下:
圖三
圖三的程式碼是暴力搜尋所有的可能,會引起 超時異常(黑話是 TLE, 全稱 Time Limit Exceeded),如下圖所示的case。
圖四
經過觀察,我們發現圖四的test case有一個特點,就是有很多重複的字元: 有很多重複的子串,在暴力演算法中有大量的重複計算。
所以,我們可以在圖三的程式碼的基礎上做優化:快取結果。
DP = 記憶優化 + 剪枝
在圖一和圖二的分析中,我們可以知道,遍歷所有的可能解的過程中 ,有許多重複的分支。
以 s = "aaaaaaaaaa", word_dic = ["aa", "aaa", "aaaa", "aaaaa"]
為例子
圖五
如圖五所示,在某一次遞迴求解的過程中,子串 "aa" 會被被重複的計算 5 次(如果是多次遞迴,計算的次數會更多)。我們可以將 "aa" 的結果快取到一個 HashTable 中,這樣就可以減少大量重複的計算。
Tips: 快取一般都是用雜湊,陣列用的比較少,因為用陣列會有空間浪費。
圖六
Tips: 自頂向下的DP其實就是暴力搜尋 + 快取。
自底向上的思路
在上面的分析中,我們可以發現一個規律:在遞迴的過程中,我們其實是將字串分成兩個子串:
- 將10個a, 分成2個a 和 8 個a 的結果的組合: "aa" + "aaaaaaaa"
- 8 個 a 又可以分成 2 個 a 和 6 個 a的組合: "aa" + "aaaaaa"
- ... ...
所以,我們可以得到如下的思路:假設 字串當前的位置是 n
,f(n)
表示所有的可能解, w(x)
表示 從位置 x
到 n
的子串:
f(n) = w(n) f(n-1) + w(n-1) f(n-2) + ... + w(0)
以 catsanddog 為例子,如下圖所示
圖七
程式碼實現如下
Tips 1: 自底向上的DP有一個小竅門:逆向思考。比如我們在分析該問題的時候,一般是考慮從起始點 0 到 結束點 i 的子串結果,這不利於推導DP公式。我們應該這樣思考:以 i 為結尾的子串作為處理的單元。這麼思考的好處,我們會自動的思考 i - 1 與 i 之間的關係,並且方便的推導他們之間的關係。
最佳提交
關於我們
每日一道演算法題是一個純粹的演算法學習社群:通過每天一起做一道演算法題來提升我們的演算法能力。
每日一題演算法群現在一共有3000位小夥伴:有來自北美和國內頂尖名校的本科生、研究生和博士生;也有來自國內外各大網際網路公司(Google, Facebook, 亞馬遜, 百度, 阿里, 騰訊等)的經驗豐富的開發者。
大家聚集只為一個目的:每天一起學習演算法!
我們堅信:學習演算法是一種信仰!
長按下面的二維碼,關注每日一道演算法題公眾號,跟我們一起學習演算法!