Leetcode演算法——30、尋找所有詞語拼接而成的子串
阿新 • • 發佈:2018-11-08
給定一個字串s、一個數組words,裡面每個元素都是一個詞語,所有詞的長度相等。
在s中尋找所有子串的索引,子串需要是words中每個詞首尾拼接而成,詞之間沒有其他字元插入,詞的拼接順序沒有要求。
示例:
Example 1: Input: s = "barfoothefoobarman", words = ["foo","bar"] Output: [0,9] Explanation: Substrings starting at index 0 and 9 are "barfoor" and "foobar" respectively. The output order does not matter, returning [9,0] is fine too. Example 2: Input: s = "wordgoodstudentgoodword", words = ["word","student"] Output: []
思路
1、暴力法
由於每個詞長度相等,因此每次取出子串的長度是固定的,而且很容易計算出所有詞語拼接起來的長度。假設所有詞拼接起來的長度為 len。
定義一個指標,從左往右遍歷s,每次遍歷都判斷當前指標開始往後的長度為 len 的子串是否符合要求:
- 對子串按照詞語長度進行切分,分成候選詞語,候選詞語的個數為陣列 words 的詞語個數。
- 對於每一個候選詞語,都與陣列 words 中的詞語進行匹配,如果正好可以一一匹配上,則說明符合要求。
小技巧:
可以將words中的每個詞的個數提前統計出來,每次匹配上一個,個數-1,直至所有的詞的個數都變為0,則說明匹配成功。
2、改進版
當某個分詞匹配不上words時,所有包含這個分詞的子串肯定都是不符合要求的,可以迅速忽略掉。
比如一個子串 ‘aaabbbccc’,被切分成了3個候選詞語 ‘aaa’,‘bbb’,‘ccc’,然後依次與words中的詞語進行匹配,結果發現words中並沒有 ‘ccc’ 這個詞語,於是這個子串匹配失敗。
但是我們還可以獲得一個資訊,就是當指標繼續向右移動3次,需要判斷 ‘bbbcccddd’ 這個子串是否符合要求時,
我們可以迅速知道肯定是不符合要求的,因為這個子串也會切分出 ‘ccc’ 這個詞語。
關鍵點在於,是指標向右移動 3 次之後的子串可以迅速判斷不符合要求,這個 3 便是每個詞語的長度。
因此,從當前指標開始,到匹配不上的候選詞語的起始位置結束,這之間的子串,按照詞語長度進行切割,得到每個詞語的起始位置,以這些位置開頭的子串都不符合條件,可以迅速略過。
python實現
import copy
import re
def findSubstring(s, words):
"""
:type s: str
:type words: List[str]
:rtype: List[int]
暴力法。
"""
if not s or not words:
return []
l_word = len(words[0]) # 每個詞的長度
l_total = l_word * len(words) # 所有詞拼接起來的長度
if len(s) < l_total: # s長度小於所有詞拼接起來的長度
return []
# 統計words中的詞頻
word_count_dict = dict()
for word in words:
if word in word_count_dict:
word_count_dict[word] += 1
else:
word_count_dict[word] = 1
# 遍歷s
result = []
for i in range(0, len(s) - l_total + 1):
cur_dict = copy.copy(word_count_dict)
split_list = re.findall(f'.{{{l_word}}}', s[i:i+l_total]) # 按詞長度切割子串
for split_word in split_list:
if split_word in cur_dict: # 可以匹配上words中的某個詞
if cur_dict[split_word] > 1: # 次數-1
cur_dict[split_word] -= 1
else:
cur_dict.pop(split_word)
# 如果詞典為空,則說明全部匹配了一遍
if not cur_dict:
result.append(i)
else: # 匹配不上,說明當前子串不合格
break
return result
def findSubstring2(s, words):
"""
:type s: str
:type words: List[str]
:rtype: List[int]
改進版。
當某個分詞匹配不上words時,所有包含這個分詞的子串肯定都是不符合要求的,可以迅速忽略掉。
"""
if not s or not words:
return []
l_word = len(words[0]) # 每個詞的長度
l_total = l_word * len(words) # 所有詞拼接起來的長度
if len(s) < l_total: # s長度小於所有詞拼接起來的長度
return []
# 統計words中的詞頻
word_count_dict = dict()
for word in words:
if word in word_count_dict:
word_count_dict[word] += 1
else:
word_count_dict[word] = 1
# 遍歷s
result = []
ignore_idx_set = set() # 肯定不符合要求的索引
for i in range(0, len(s) - l_total + 1):
if i in ignore_idx_set:
continue
cur_dict = copy.copy(word_count_dict)
for j in range(0, len(words)): # 每次遍歷子串的一個分詞
split_word_start = i + j*l_word # 分詞的起始索引
split_word = s[split_word_start : split_word_start + l_word] # 子串的第j個分詞
if split_word in cur_dict: # 可以匹配上words中的某個詞
if cur_dict[split_word] == 0: # 次數已經用盡,說明此詞是多餘的,子串不符合要求
break
cur_dict[split_word] -= 1
if j == len(words) - 1: # 已經遍歷完最後一個分詞,說明子串符合要求
result.append(i)
else: # 分詞不存在於words中,則所有包含這個分詞的子串都肯定不符合要求
k = i + l_word # 包含這個分詞的子串的起始位置
while(k <= split_word_start):
ignore_idx_set.add(k)
k += l_word
break
return result
if '__main__' == __name__:
s = "wordgoodgoodgoodbestword"
words = ["word","good","best","good"]
print(findSubstring2(s, words))