版本一,暴力解法,虽然超时,其中有些步骤值得学习
// 暴力解法:遍历数组的所有的区间,然后找到最长没有重复字符的区间
// 时间复杂度:O(n^3)
// 空间复杂度:O(n)
func lengthOfLongestSubstring1(s string) int {
var n = len(s)
if n <= 1 {
return n
}
var allUnique func(start, end int) bool
allUnique = func(start, end int) bool { //判断区间[start, end]有无重复元素
var set = make(map[byte]bool)//这里先声明,直接读会返回nil
for i := start; i <= end; i++ {
if set[s[i]] {
return false
}
set[s[i]] = true
}
return true
}
var maxLen = 1
for i := range s {
for j := i + 1; j < n; j++ {
if allUnique(s, i, j) && j - i + 1 > maxLen {//这里还有个优化呢
maxLen = j - i + 1
}
}
}
return maxLen
}
暴力解法存在重复计算,同一个子串会进行多次判断 【是否存在重复字符】
我们可以做如下的优化:
如果 s[i, j) 子串没有重复字符的话,那么如果要判断 s[i, j] 没有重复字符的话,我们只需要判断s[i, j) 中是否存在 s[j] 即可
// 优化后的滑动窗口
// 时间复杂度:O(n)
// 空间复杂度:O(n)
func lengthOfLongestSubstring3(s string) int {
var n = len(s)
if n <= 1 {
return n
}
maxLen := 1
left, right, window := 0, 0, make(map[byte]int)
for right < n {
rightChar := s[right]
rightCharIndex := 0
if _, ok := window[rightChar]; ok { //判断s[i, j)中是否有s[j]
rightCharIndex = window[rightChar] //如果窗口有了,返回一个索引
}
if rightCharIndex > left { //更新left
left = rightCharIndex
}
if right - left + 1 > maxLen { //更新maxLen
maxLen = right - left + 1
}
window[rightChar] = right + 1 //map中key是每个byte,value
是当前窗口后一位
right++
}
return maxLen
}
精简代码,思路清晰,最终版本
func lengthOfLongestSubstring(s string) int {
var n = len(s)
//边界处理
if n <= 1 {
return n
}
//初始化
var maxLen = 1
var left, right, window = 0, 0, [128]int{}
//这里得[]int{},位置==字符ASCII,内容存right+1
for right < n {
var rightChar = s[right]
var rightCharIndex = window[rightChar]
if rightCharIndex > left { //这种情况只发生在第二次相遇
left = rightCharIndex
}
maxLen =max(right-left+1, maxlen)
window[rightChar] = right + 1 //存得是当前维护得窗口下一位
right++
}
return maxLen
}