第一次写是用暴力解,做出来了。
大概思路:循环遍历字符串1,然后比对字符串2,设置一个计数器count,当第一个元素能对上,那就各自下标++,计数器也++。直到count为字符串2的长度就可以返回正确结果。
func strStr(haystack string, needle string) int {
runes1:=[]rune(haystack)
runes2:=[]rune(needle)
for i:=0;i<len(runes1);i++{
tempi:=i
count:=0
j:=0
for tempi<len(runes1) && j< len(runes2) && runes1[tempi]==runes2[j]{
tempi++
j++
count++
}
if count == len(runes2){
return i
}
}
return -1
}
我写的时候就只出现一个问题,这个循环条件我写的时候一开始写漏了,然后又有条件的顺序问题。
tempi
想法改进:
我想用go语言的for range,然后直接遍历字符串从而达到降低空间复杂度的目的。
利用go语言性质的版本
func strStr(haystack string, needle string) int {
n := len(needle)
if n == 0 {
return 0
}
for i := range haystack {
if i+n > len(haystack) {
break
}
if haystack[i:i+n] == needle {
return i
}
}
return -1
}
这个版本让我学会了go语言性质的一些操作。
1.len()这个函数可以对很多数据类型使用来返回,之前我总是习惯用数组,所以对别的数据类型了解过少。
字符串 (String): 返回字符串的字节长度。
切片 (Slice): 返回切片中元素的数量。
数组 (Array): 返回数组中元素的数量。
映射 (Map): 返回映射中键值对的数量。
通道 (Channel): 返回缓冲通道中元素的数量(非缓冲通道将返回0)。
对字符串使用要注意以下:返回的是字符串的字节长度,而不是字符长度。
举个例子:len(“hello”)和len(“你好”),如果是字符长度,那么你计算就会得到5和2,但是由于返回的是字节长度,hello是ASCII字符,每个字符是一字节,所以前者返回的长度还是5,但是后者就不一样,“你好”是中文字符,每个中文字符在UTF-8编码中通常占3个字节,因此这里计算后返回的长度应该是6.
之前学len()函数的时候没有太注意这些细节,这里要注意。
2.haystack[i:i+n] == needle
我之前学习的是对字符串是不允许进行下标操作的,这里要进行进一步的说明了,haystack[i]这种对字符串单个字符的下标访问是不允许的,但是可以使用切片的操作来访问字符串的子串,haystack[i:i+n]实际上是对字符串haystack进行切片操作。这个是go语言对字符串进行切片操作的一个特性,允许你获取原始字符串的一部分,而无需创建一个新的字符串和数组。
在这个比较操作中:haystack[i:i+n]创建了一个haystack子串,然后将其与needle进行比较。
底层解析:在go语言中,字符串本质上被视为字节序列,而切片操作可以应用于这种序列。所以这种操作才变得合法。
haystack[i:i+n] == needle进一步解析:我当时还好奇这个操作的时间复杂度,因为如果这个操作的时间复杂度是o(1)那不久更快了,但是并非如此。
这个比较的实质是,在比较的时候逐字节比较,所以时间复杂度方面通常是o(n),当发现任何点的字节不匹配,比较就停止。
综合这个版本的时间复杂度还是和版本1一样,但是空间辅助度被优化成了o(1).
版本3:
看的这个找子序列,那肯定最好的就是kmp算法。这也是这个题的最终目的。这里我后面再学。
我拿到这个题的第一想法还是暴力解
遍历每一种子串的可能然后拿去在s字符串上扫一次。
func repeatedSubstringPattern(s string) bool {
l:=len(s)
for i:=0;i<l/2;i++{
tempstr:=s[0:i+1]
j:=0
for j<l{
if j+i+1>l||s[j:j+i+1]!=tempstr{
break
}else{
j+=i+1
}
}
if j==l{
return true
}
}
return false
}
这个题我在边界处理上还是有很多的问题,然后我才调整边界的情况
我第一次写的问题:
1.外层for循环,第一次我有点没脑子了,其实子串最多只取到字符串的一半,因为多了也不符合题意。所以i
还有改进版,这个是基于题目的性质来进行思考的:
func repeatedSubstringPattern(s string) bool {
n := len(s)
for i := 1; i <= n/2; i++ {
if n%i == 0 {
tempstr := s[:i]
j := i
for j < n {
if s[j:j+i] != tempstr {
break
}
j += i
}
if j == n {
return true
}
}
}
return false
}
1.我之前版本的代码有个人为+1的操作,这里我认为不太好,于是这里我就做了修改,有了+1的性质,从而避免我自己去+1。
for i := 1; i <= n/2; i++,这里取等需要自己去模拟到底要不要取等。举个偶数的例子即可。
2.加了一个特判,取模运算不能为0那么肯定过不了。
3.j := i这里,我令j从i的位置开始。因为tempstr取的就是s[:i],前面相当于取的时候就已经判断了,所以这里从i开始可以少判一次。后面都是相同的操作。
4.这个题本身不用考虑数组越界,而是我在2.特判这里已经考虑了。这里进行了这样的处理,就不会发生越界异常,而是刚好能够访问完每个安全的位置。
时间复杂度:o(n*logn)
这个题本身就是kmp的应用
在go语言中,字符串非常的需要去学习它的在这个语言中的特定。因为之前一直用的c++,但是我在学习go语言的过程中,感受到go的字符串和c++的字符串还是有很多区别的,虽然在做题的算法思想上一样,但是在用法上还是有很多的不同。
这里进行了六点go语言的字符串总结:
在 Go 语言中,字符串是不可变的字节序列,通常用于表示文本。字符串的特点包括:
1.UTF-8 编码:Go 字符串默认使用 UTF-8 编码。这意味着一个字符串可以包含任何 Unicode 字符。
2.不可变性:字符串一旦创建,其内容就不能更改。要修改字符串,需创建一个新的字符串。
3.切片操作:虽然不能直接通过下标修改字符串中的字符,但可以通过切片操作来访问字符串的子串。切片操作是左闭右开的。
4.长度:使用 len() 函数可以获得字符串的字节长度,而不是字符数。对于包含多字节字符的字符串,字节长度可能大于字符数。
5.遍历:可以使用 for range 循环来遍历字符串中的字符,这样做会正确处理 UTF-8 编码的多字节字符。
6.字符串与字节数组/切片的转换:可以将字符串转换为字节数组([]byte)或字符数组([]rune),以进行更复杂的操作。
写了十个题,发现每个性质都在题目中有体现,所以掌握这六个点是十分重要的。
非常适合用来字符串的翻转操作,在操作字符串中有很大的用途
体现在链表翻转,和快慢指针。