题目描述
给定一个字符串 s 和一些长度相同的单词 words。找出 s 中恰好可以由 words 中所有单词串联形成的子串的起始位置。
注意子串要与 words 中的单词完全匹配,中间不能有其他字符,但不需要考虑 words 中单词串联的顺序。
示例
示例1(基本功能测试)
输入:
s = “barfoothefoobarman”,
words = [“foo”,“bar”]
输出:[0,9]
解释:
从索引 0 和 9 开始的子串分别是 “barfoor” 和 “foobar” 。
输出的顺序不重要, [9,0] 也是有效答案。
示例2(边界值测试)
输入:
s = “wordgoodgoodgoodbestword”,
words = [“word”,“good”,“best”,“word”]
输出:[]
示例3(特殊输入测试)
输入:
s = “foobarfoobar”,
words = [“foo”,“bar”]
输出:[0, 3, 6]
示例4(性能测试)
输入:
s = “pjzkrkevzztxductzzxmxsvwjkxpvukmfjywwetvfnujhweiybwvvsrfequzkhossmootkmyxgjgfordrpapjuunmqnxxdrqrfgkrsjqbszgiqlcfnrpjlcwdrvbumtotzylshdvccdmsqoadfrpsvnwpizlwszrtyclhgilklydbmfhuywotjmktnwrfvizvnmfvvqfiokkdprznnnjycttprkxpuykhmpchiksyucbmtabiqkisgbhxngmhezrrqvayfsxauampdpxtafniiwfvdufhtwajrbkxtjzqjnfocdhekumttuqwovfjrgulhekcpjszyynadxhnttgmnxkduqmmyhzfnjhducesctufqbumxbamalqudeibljgbspeotkgvddcwgxidaiqcvgwykhbysjzlzfbupkqunuqtraxrlptivshhbihtsigtpipguhbhctcvubnhqipncyxfjebdnjyetnlnvmuxhzsdahkrscewabejifmxombiamxvauuitoltyymsarqcuuoezcbqpdaprxmsrickwpgwpsoplhugbikbkotzrtqkscekkgwjycfnvwfgdzogjzjvpcvixnsqsxacfwndzvrwrycwxrcismdhqapoojegggkocyrdtkzmiekhxoppctytvphjynrhtcvxcobxbcjjivtfjiwmduhzjokkbctweqtigwfhzorjlkpuuliaipbtfldinyetoybvugevwvhhhweejogrghllsouipabfafcxnhukcbtmxzshoyyufjhzadhrelweszbfgwpkzlwxkogyogutscvuhcllphshivnoteztpxsaoaacgxyaztuixhunrowzljqfqrahosheukhahhbiaxqzfmmwcjxountkevsvpbzjnilwpoermxrtlfroqoclexxisrdhvfsindffslyekrzwzqkpeocilatftymodgztjgybtyheqgcpwogdcjlnlesefgvimwbxcbzvaibspdjnrpqtyeilkcspknyylbwndvkffmzuriilxagyerjptbgeqgebiaqnvdubrtxibhvakcyotkfonmseszhczapxdlauexehhaireihxsplgdgmxfvaevrbadbwjbdrkfbbjjkgcztkcbwagtcnrtqryuqixtzhaakjlurnumzyovawrcjiwabuwretmdamfkxrgqgcdgbrdbnugzecbgyxxdqmisaqcyjkqrntxqmdrczxbebemcblftxplafnyoxqimkhcykwamvdsxjezkpgdpvopddptdfbprjustquhlazkjfluxrzopqdstulybnqvyknrchbphcarknnhhovweaqawdyxsqsqahkepluypwrzjegqtdoxfgzdkydeoxvrfhxusrujnmjzqrrlxglcmkiykldbiasnhrjbjekystzilrwkzhontwmehrfsrzfaqrbbxncphbzuuxeteshyrveamjsfiaharkcqxefghgceeixkdgkuboupxnwhnfigpkwnqdvzlydpidcljmflbccarbiegsmweklwngvygbqpescpeichmfidgsjmkvkofvkuehsmkkbocgejoiqcnafvuokelwuqsgkyoekaroptuvekfvmtxtqshcwsztkrzwrpabqrrhnlerxjojemcxel”
words =
[“dhvf”,“sind”,“ffsl”,“yekr”,“zwzq”,“kpeo”,“cila”,“tfty”,“modg”,“ztjg”,“ybty”,“heqg”,“cpwo”,“gdcj”,“lnle”,“sefg”,“vimw”,“bxcb”]
输出:[935]
分析
一开始我想到的算法是:先用递归求出words数组中所有单词组合的全排列,并存入一个数组中,然后在字符串s中进行查找,判断数组中的元素是否是s的子串(可以使用Go自带的strings包中的Contains函数),如果是则记下子串首字符的下标。这种算法的主要复杂度集中于求words数组元素的全排列,当words数组的长度较长时,算法的时间复杂度极高,示例4是肯定过不了的。
本题的关键在于这些单词的长度相同,这是一个很关键的条件,利用此条件,我们可以取words[0]这个字符串,借助它的下标进行遍历,可遍历完words中所有的字符串。我们可以使用两个Map来保存出现的字符串和次数。首先,在外循环中,遍历给定字符串s直到总长度减去单个待查找字符的长度(len(s) - len(words) * len(words[0])
);然后,在内循环中,按照每个单词的长度len(words[0])
对给定字符串s进行切割,分别提取子串s[i + num * length : i + (num + 1) * length]
,其中i
为字符串s的字符索引,num
用来维护当前匹配的次数,length
即len(words[0])
。如果words数组中包含该子串,则num
加1,继续查询;如果不包含该子串,则跳出内循环,从给定字符串s的下一个字符开始查询。最后,当内循环结束后,判断num
是否等于len(words)
(因为子串恰好是words数组里的元素的一种组合),如果是,则保存起始下标到结果数组中。
注意:Go语言中Map没有清空函数,清空一个Map的唯一方法是重新定义一个新的Map。
代码
正确答案
本文测试结果均由以下代码运行得到:
package main
import "fmt"
func main() {
s := "barfoothefoobarman"
words := []string{"foo","bar"}
//s := "wordgoodgoodgoodbestword"
//words := []string{"word","good","best","word"}
res := findSubstring(s, words)
fmt.Println(res)
}
func findSubstring(s string, words []string) []int {
var res []int
if s == "" || len(s) == 0 || words == nil || len(words) == 0 {
return res
}
// words数组中每个单词字符串长度一致, 取第一个单词
length := len(words[0])
// 定义一个wordsMap用于存放words数组中每个单词出现的次数
wordsMap := make(map[string]int)
for _, w := range words {
put(wordsMap, w)
}
for i := 0; i < len(s) - len(words) * length + 1; i++ {
// 定义一个window用于存放匹配的单词出现的次数
window := make(map[string]int)
// 定义一个num维护当前匹配的次数
var num int
for num < len(words) {
// 根据查找取子串
word := s[i + num * length : i + (num + 1) * length]
_, ok := wordsMap[word]
if ok {
put(window, word)
// window中word出现的次数超过words数组中,则跳出本次循环
if window[word] > wordsMap[word] {
break
}
} else {
// 没有查询到, 跳出本次循环, 查找下一个字符
break
}
num++
}
if num == len(words) {
res = append(res, i)
}
}
return res
}
func put(myMap map[string]int, key string) {
_, ok := myMap[key]
if ok {
myMap[key] += 1
} else {
myMap[key] = 1
}
}
超时答案
即分析一节开始给出的解法,该解法无法在规定时间内通过示例4,其余三个示例均能通过。以下给出代码仅供参考。
package main
import (
"fmt"
"strings"
"sort"
)
func main() {
s := "foobarfoobar"
words := []string{"foo","bar"}
//s := "wordgoodgoodgoodbestword"
//words := []string{"word","good","best","word"}
res := findSubstring(s, words)
fmt.Println(res)
}
func findSubstring(s string, words []string) []int {
var str []string
var res []int
permutation(words, 0, &str)
for _, r := range str {
res = append(res, index(s, r)...)
}
sort.Ints(res)
return res
}
func permutation(words []string, begin int, pStr *[]string) {
if words == nil || len(words) == 0 || begin < 0 || begin > len(words) - 1 {
return
}
if begin == len(words) - 1 {
strNew := strings.Join(words, "")
if !contains(*pStr, strNew) {
*pStr = append(*pStr, strNew)
}
} else {
for i := 0; i < len(words); i++ {
words[begin], words[i] = words[i], words[begin]
permutation(words, begin + 1, pStr)
words[begin], words[i] = words[i], words[begin]
}
}
}
func contains(str []string, s string) bool{
for _, r := range str {
if s == r {
return true
}
}
return false
}
func index(s1, s2 string) []int{
var indexes []int
if strings.Contains(s1, s2) {
for i := 0; i <= len(s1) - len(s2); i++ {
if s1[i: i + len(s2)] == s2 {
indexes = append(indexes, i)
}
}
}
return indexes
}
运行结果
示例1(基本功能测试)
示例2(边界值测试)
示例3(特殊输入测试)
示例4(性能测试)