Leetcode解题之路(golang版):3. 无重复字符的最长子串(Longest Substring Without Repeating Characters)

3. 无重复字符的最长子串

给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。

示例 1:
输入: "abcabcbb"
输出: 3 
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。

示例 2:
输入: "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。

示例 3:
输入: "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
     请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。

解法一:

使用二分法配合滑动窗口法

空间复杂度:$O(n)$

时间复杂度:$O(nlogn)$

func judge(s string, length int) bool {
    //flag记录重复的字符数量
    flag := 0
    m := make(map[byte]int)
    //构造map,key代表字符,value代表在lenght的窗口长度下,key出现了多少次
    for i := 0; i < length; i++ {
        if _, ok := m[s[i]]; ok {
            m[s[i]] = m[s[i]] + 1
            //如果一个字符出现了超过两次,说明该字符重复了
            if m[s[i]] == 2 {
                flag++
            }
            continue
        }
        m[s[i]] = 1
    }
    //如果没有重复的字符直接返回true
    if flag == 0 {
        return true
    }
    for i := length; i < len(s); i++ {
        //新增的字符
        if _, ok := m[s[i]]; ok {
            m[s[i]] = m[s[i]] + 1
            if m[s[i]] == 2 {
                flag++
            }
        } else {
            m[s[i]] = 1
        }
        //删除原来的字符
        m[s[i - length]] = m[s[i - length]] - 1
        if m[s[i - length]] == 1 {
            flag--
        }
        if flag == 0 {
            return true
        }
    }
    return false
}


func lengthOfLongestSubstring(s string) int {
    if len(s) <= 1 {
        return len(s)
    }
    l, r, mid := 0, len(s), 0
    //二分法找最大不重复子串
    for l < r {
        mid = (l + r + 1) >> 1
        if judge(s, mid) {
            l = mid
        } else {
            r = mid - 1
        }
    }
    return l
}

解法二:

空间复杂度:$O(n)$

时间复杂度:$O(n2)$

出于好奇,我试了一下不使用二分法,顺序查找最大不重复子串,发现竟然耗时小于使用二分法,应该是测试数据的问题

func judge(s string, length int) bool {
    flag := 0
    m := make(map[byte]int)
    for i := 0; i < length; i++ {
        if _, ok := m[s[i]]; ok {
            m[s[i]] = m[s[i]] + 1
            if m[s[i]] == 2 {
                flag++
            }
            continue
        }
        m[s[i]] = 1
    }
    if flag == 0 {
        return true
    }
    for i := length; i < len(s); i++ {
        //新增的字符
        if _, ok := m[s[i]]; ok {
            m[s[i]] = m[s[i]] + 1
            if m[s[i]] == 2 {
                flag++
            }
        } else {
            m[s[i]] = 1
        }
        //删除原来的字符
        m[s[i - length]] = m[s[i - length]] - 1
        if m[s[i - length]] == 1 {
            flag--
        }
        if flag == 0 {
            return true
        }
    }
    return false
}


func lengthOfLongestSubstring(s string) int {
    if len(s) <= 1 {
        return len(s)
    }
    l := -1
    //顺序查找
    for i := 0; i <= len(s); i++ {
        if judge(s, i) {
            l = i
        } else {
            break
        }
    }
    return l
}

但是其实这种滑动窗口的方式不够优化,即使配合上二分法也只能达到nlogn的时间复杂度,我们可以再继续优化一下。

解法三:

map中记录的为最后一个字符出现的下标,每次当有新字符和之前的重复之后,就把last更新为最后一次出现的字符下标加1。

通过这种方式保证了当前窗口last到i不存在重复的字符

时间复杂度仅为O(n)

空间复杂度:$O(1)$

时间复杂度:$O(n)$

func find(s string) int {
    last := 0
    max := 0
    m := make(map[byte]int)
    for i := 0; i < len(s); i++ {
        if _, ok := m[s[i]]; ok && m[s[i]] >= last {
            last = m[s[i]] + 1
            m[s[i]] = i
            continue
        }
        m[s[i]] = i
        if i - last + 1 > max {
            max = i - last + 1
        }
    }
    return max
}

func lengthOfLongestSubstring(s string) int {
    if len(s) <= 1 {
        return len(s)
    }
    return find(s)
}

你可能感兴趣的:(leetcode)