LeetCode 30 - Substring with Concatenation of All Words
題目
題目連結:https://leetcode.com/problems/substring-with-concatenation-of-all-words/
給定一個字串 s 和一個 words 陣列,words 內包含長度一樣的單詞。
找到所有的子字串的開頭索引值,子字串必須是 words 中的每一個單詞各出現一次的連接字串。
範例說明
Example 1
1 | Input: |
Example 2
1 | Input: |
想法
假設 wl 為每一個單詞的長度。
要找的子字串必須相連,所以將字串 s 分為 wl 個集合,分別為:
0,0 + wl,0 + 2 * wl,0 + 3 * wl…1,1 + wl,1 + 2 * wl,1 + 3 * wl…- …
(wl - 1),(wl - 1) + wl,(wl - 1) + 2 * wl,(wl - 1) + 3 * wl…
例如 Example 1 中的 s = "barfoothefoobarman", words = ["foo", "bar"], 則可以分為 { "bar", "foo", "the", "foo", "bar", "man" }, { "arf", "oot", "hef", "oob", "arm" }, { "rfo", "oth", "efo", "oba", "rma" } 三個集合。
答案必定為同一個集合內的連續一段子字串。
在一個集合之中,利用 Sliding Window 找尋答案。所謂 Sliding Window 是指利用兩個指標標示目前的範圍,或是稱作「窗口」。原本窗口內為空集合,從第一個字串開始,將字串加入窗口(窗口的右側增加,窗口變大),如果當前的字串加入後,窗口內的字串們無法形成答案,就逐次將窗口左側向右(窗口變小),直到窗口內的字串們可以形成答案,或是窗口為空。
要判斷窗口內的答案可否形成答案,也就是要判斷窗口內的子字串們是不是 words 的子集合。再者,若窗口內的子字串們恰好等於 words 的單詞集合,那窗口最左側的字串之索引值就是一個合法答案。
一樣以 Example 1 為例子,一開始窗口為 []。
"bar"-> 將"bar"加入窗口,窗口為["bar"],是words的子集合。"foo"-> 將"foo"加入窗口,窗口為["bar", "foo"],恰好等於words,所以窗口左側"bar"的索引為一組答案。"the"-> 將"the"加入窗口,窗口為["bar", "foo", "the"],不為words的子集合,依次將左側窗口向右直到窗口為words之子集合或是窗口為空,["bar", "foo", "the"] -> ["foo", "the"]-> ["the"] -> []。"foo"-> 將"foo"加入窗口,窗口為["foo"],是words的子集合。"bar"-> 將"bar"加入窗口,窗口為["foo", "bar"],恰好等於words,所以窗口左側"foo"的索引為一組答案。"man"-> 將"man"加入窗口,窗口為["foo", "bar", "man"],不為words的子集合,依次將左側窗口向右直到窗口為words之子集合或是窗口為空,["foo", "bar", "man"] -> ["bar", "man"]-> ["man"] -> []。
實作細節
為了維護窗口內的子集合,可以使用 C++ STL unordered_multiset,unordered_multiset 為使用 Hashmap 實作的集合,可以 O(1) 加入、刪除、查找一個值。與 unordered_set 不同的是,unordered_set 視相同的值為集合內的同一個元素,但 unordered_multiset 視同一個值為集合內的不同元素。恰好符合 words 內可能有相同單詞的性質。
假設 dict 為一 unordered_multiset,初始為空。
實作上,先將 words 內的單詞都先加入 dict 之中,當新的子字串要加入窗口,就必須在 dict 中找到此子字串才代表此子字串加入後窗口會是 words 的子集合。子字串加入後,從 dict 中刪除此子字串,若 dict 為空,說明當前窗口恰好等於 words,左側窗口索引值紀錄。當左側窗口向右移動時,則把離開窗口的子字串加回 dict 之中。
完成後不忘把窗口內的元素都加回 dict 之中,讓 dict 恢復成原本的樣子,以方便下一組的集合使用。
假設 N = 字串 s 長度,每一個子字串只會被加入 Sliding Window 一次,只會從 Sliding Window 中被移除一次,加入、刪除的時間複雜度均是 O(1)。所以總時間複雜度為 O(2N) = O(N)。
不過 C++ 的 String.substr 時間複雜度為 O(String Length),所以總時間複雜度應該為 O(wl * N)。
程式碼
1 | /** |