算法day8

算法day8

  • 28 找出字符串中第一个匹配的下标
  • 459 重复的子字符串。
  • 字符串总结
  • 双指针总结

28找出字符串中第一个匹配的下标

第一次写是用暴力解,做出来了。
大概思路:循环遍历字符串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 我最开始没有进行tempi和j的越界判断,因为要保证下标合法,所以这里要加前两个合法判断。而且根据短路的运算,这两个条件必须要在后面那个相等判断之前,否则还是有数组越界的风险。

想法改进:
我想用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算法。这也是这个题的最终目的。这里我后面再学。


459 重复的子字符串

我拿到这个题的第一想法还是暴力解
遍历每一种子串的可能然后拿去在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 2.if j+i+1>l||s[j:j+i+1]!=tempstr,这个条件我第一次写的是s[j:j+i]!=tempstr,我后来模拟的时候就发现问题了,由于我赋值i是从0开始的,所以这里我这里要+1,和上面我赋值tempstr一样,那里我考虑了,下面这里我忘记考虑了。j+i+1>l是为了判断提取的字符要与tempstr对的上。

还有改进版,这个是基于题目的性质来进行思考的:

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)

解法3 kmp

这个题本身就是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),以进行更复杂的操作。

写了十个题,发现每个性质都在题目中有体现,所以掌握这六个点是十分重要的。


双指针总结:

字符串:

非常适合用来字符串的翻转操作,在操作字符串中有很大的用途

链表

体现在链表翻转,和快慢指针。

N数之和都可做

你可能感兴趣的:(算法,开发语言,golang,数据结构)