刚开始的思路,先不管效率,跑出来再说,然后再进行优化。然后就有了下面的暴力代码:
func lengthOfLongestSubstring(s string) int {
// count 用来记录当前最长子串长度
var count int
// flag 用来对下面两个 if 语句分流
var flag int = 0
// for 对字符串进行遍历
for i := 0; i < len(s); i++ {
// mark 用来记录当前子串,初始为当前遍历遇到的第一个字符
mark := string(s[i])
// 这个 for 循环用来接着 mark 往后遍历,如果遇到的字符不在 mark 里,就往 mark 后面接
for j := i + 1; j < len(s); j++ {
// strings.Contain(A string, B string) 用来判断 A 中是否有 B,有返回 true, 没有返回 false
if strings.Contains(mark, string(s[j])) == true {
// 有的话就 break, 当前是一个完整的无重复子串,再往后接就重复了
break
}
if strings.Contains(mark, string(s[j])) == false {
// 没有说明还能接着往后接,就继续遍历
mark += string(s[j])
}
}
if flag == 0 {
// flag == 0 表示这是该字符串遇到的第一个无重复子串,把长度记录下来
count = len(mark)
flag = 1
} else if flag == 1 {
// flag == 1 表示这以及不是第一个子串了,那么就计算当前无重复子串的长度
// 和原先比较,更长的话就重新赋值给 count, 否则就不重新赋值
if count < len(mark) {
count = len(mark)
}
}
}
return count
}
跑是跑出来了,时间 300ms (仅超过 5% 用户),内存 6.44MB (仅超过 7% 用户),那我得优化以下。我寻思能不能用字符指针,让源代码减少一个 for 循环?emmm 然后我就开始写代码,但是我发现指针并不能减少一个 for 循环,因为始终需要一个 for 来遍历子串起始位置,另一个 for 用来移动指针,写都写了,就上代码吧:
package main
import (
"fmt"
"strings"
"unsafe"
)
func lengthOfLongestSubstring(s string) int {
// 先将字符串变成字符数组,采用用指针遍历
bytes := []byte(s)
// 定义字符指针
var bytePtr *byte
// flag 和 count 的作用上一版程序说过了,不赘述
var flag int = 0
var count int
for i := 0; i < len(s); i++ {
mark := string(s[i])
for j := i + 1; j < len(s); j++ {
// 指针指向 bytes[j] 位置的元素
// golang 这种字符指针很麻烦,必须用 unsafe.Pointer() 进行转换
bytePtr = (*byte)(unsafe.Pointer(&bytes[j]))
if strings.Contains(mark, string(*bytePtr)) == false {
// 如果该字符不在前面的子串中,则加入
mark += string(*bytePtr)
// 指针后移一位
bytePtr = (*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(bytePtr)) + 1))
}
if strings.Contains(mark, string(*bytePtr)) == true {
break
}
}
if flag == 0 {
count = len(mark)
flag = 1
} else if flag == 1 {
if count < len(mark) {
count = len(mark)
}
}
}
return count
}
func main() {
var s = "pwwkew"
fmt.Println(lengthOfLongestSubstring(s))
}
笑死了,时间 216ms (仅超过 5.84% 用户),内存 6.46MB (仅超过 6.98% 用户),几乎没有优化。我想着看看大佬都是怎么写的吧。发现大佬用的是滑动窗口,确实酷,来个大佬讲解视频的链接 点这里跳转,然后下面是代码,看不懂的朋友可以进行单步调试,我是边调边画图理解的。该程序运行时间 0ms,占用内存 2.26MB ,比我原方案效率高太多了,妙不可言。
package main
import (
"fmt"
)
func lengthOfLongestSubstring(s string) (ans int) {
window := [128]bool{} // 也可以用 map,这里为了效率用的数组
left := 0
for right, c := range s {
for window[c] { // 加入 c 后,窗口内会有重复元素
window[s[left]] = false
left++
}
window[c] = true
ans = max(ans, right-left+1) // 更新窗口长度最大值
}
return
}
func max(a, b int) int {
if b > a {
return b
}
return a
}
func main() {
var s = "pwwkew"
fmt.Println(lengthOfLongestSubstring(s))
}