给定一个字符串 s 和一些长度相同的单词 words。在 s 中找出可以恰好串联 words 中所有单词的子串的起始位置。
注意子串要与 words 中的单词完全匹配,中间不能有其他字符,但不需要考虑 words 中单词串联的顺序。
示例 1:
输入:
s = "barfoothefoobarman",
words = ["foo","bar"]
输出: [0,9]
解释: 从索引 0 和 9 开始的子串分别是 "barfoor" 和 "foobar" 。
输出的顺序不重要, [9,0] 也是有效答案。
示例 2:
输入:
s = "wordgoodstudentgoodword",
words = ["word","student"]
输出: []
思路:这道题想要通过find找出words中各元素的位置然后判断这个位置是否是符合条件的等差数列来判断基本不能实现。
所以这道题目前我提交的思路是:每次从s中切对应长度(words中各个元素的长度之和)的一条子串,然后再把这条子串按words中单个单词的长度再切成更小子串构成一个list,把words和list排序后的结果比对一下,看是否完全相同,如果是则我们返回对应的起始点。否则继续。
class Solution:
def split(self,string,width):#将一个字符串string按着width的宽度切开放在一个列表中,返回这个列表。
result = []
i = 0
length = len(string)
while i<=length-width:
result.append(string[i:i+width])
i = i+width
return result
def findSubstring(self, s, words):
"""
:type s: str
:type words: List[str]
:rtype: List[int]
"""
result = []
words_count = len(words)
if words_count>0:#判断输入的s和words是否为空,如果不为空,将words中的单词的宽度放在length_word中
length_word = len(words[0])
else:
length_word = 0
i= 0
length_s = len(s)
if length_s == 0 or words_count == 0:#如果s为空或者words为空,返回空的列表
return []
while i <= length_s-length_word*words_count:#利用while循环,实现对s遍历
string_list = self.split(s[i:i+length_word*words_count],length_word)#将s从i开始切分出一个长度和words中所有单词加在一起长度相同的一个子串,并将这个子串切开,放在string_list中
string_list.sort()#由于words中的单词并不是排好序的,所以这里需要调用两个sort函数,将这两个列表排序,这样才能够判断他们是否相等。
words.sort()
if string_list == words:#如果不是排好序的列表,即使里面的元素都相等,但是顺序不等的话,也是不会相等的。
result.append(i)
i = i + 1
return result
其中split函数是用来把我们得到的子串再进行更小的切分的,即把子串拆成words里的单个元素。
而i是子串的起始位置,我们知道子串的总长度,所以我们可以聪明地不用去遍历所有字符串的index,当我们目前的index距离尾部的index的距离小于应该需要的总长度时,我们的遍历就结束,因为接下来的子串连长度都不符合,所以果断放弃。
总结:这种方法有点暴力。总的来说就是遍历所有可能的相同长度的子串并保存起来,然后把这子串再拆分进行更精细的判断。
第二种,看了下大神的解法,简单理解了一下:
class Solution(object):
def findSubstring(self, s, words):
"""
:type s: str
:type words: List[str]
:rtype: List[int]
"""
if not s or words==[]:
return []
lenstr=len(s)
lenword=len(words[0])
lensubstr=len(words)*lenword
times={}
#统计words中各词出现的频率
for word in words:
if word in times:
times[word]+=1
else:
times[word]=1
#储存结果
ans=[]
#为什么它这个算法快呢?因为它遍历的的少,我得遍历N遍,但它这个算法不用遍历太多
#假设lenword = 4, 它相当于只关注0, 1, 2, 3,因为这些数字加lenword之后可以等于新开始的子串的index。
#所以这种思路与我的遍历思路截然不同了。
for i in range(min(lenword,lenstr-lensubstr+1)):
self.findAnswer(i,lenstr,lenword,lensubstr,s,times,ans)
return ans
def findAnswer(self,strstart,lenstr,lenword,lensubstr,s,times,ans):
wordstart=strstart
curr={}
#因为前面的遍历数量减少,所以它寻找的这个函数无论怎样都要走到尽头末尾。以确定我们这个ststart能够
while strstart+lensubstr<=lenstr:
word=s[wordstart:wordstart+lenword]
wordstart+=lenword
if word not in times:
strstart=wordstart
curr.clear()
else:
if word in curr:
curr[word]+=1
else:
curr[word]=1
#如果有多的则把我们多的那部分丢弃
while curr[word]>times[word]:
#原子串头部那一节出现频率减一
curr[s[strstart:strstart+lenword]]-=1
#移动子串头部,原子串头部被丢弃
strstart+=lenword
if wordstart-strstart==lensubstr:
ans.append(strstart)
突破点在与这算法遍历的比我少,他每次都只遍历最多lenword次。
反思总结:这种等差切分字符串的题,我们可以只遍历前几项,因为我们通过差可以找到后面的项。