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

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

示例1:

输入:s = "abcabcbb"

输出:3

解释:因为无重复字符的最长子串是"abc",所以其长度为3。

示例2:

输入:s = "bbbbb"

输出:1

解释:因为无重复字符的最长子串是"b",所以其长度为1。

示例3:

输入:s = "pwwkew"

输出:3

解释:因为无重复字符的最长子串是"wke",所以长度为3。

示例4:

输入:s = ""

输出:0

 1. 思考

解决思路很简单,依次遍历所有字符作为起始位置,找到所有可能的不重复字符子串,即可以得知其中最长子串的长度。

比如需要判断字符串"abcabcbb"。

在该思路下,有两种实现方案,一是常规解法,一是优化方案,滑动窗口。

其中常规解法就是每次更新起始索引,从该索引开始依次往后匹配,找到最长无重复字符子串。但是这种解法其实每次都会存在重复工作。

从上图匹配步骤可以发现,随着查找的起始位置递增,结束位置也是递增的,比如上次找到begin ~ end间字符是不重复的,那么begin + 1 ~ end间字符也必定是不重复的,而且begin + 1 ~ end去除了begin对应的字符,所以end可以向后继续尝试匹配,直到找到非重复字符。这就是滑动窗口思想,这边本质其实就是复用了上一次匹配得到的信息,避免重复工作。

 

 2. 常规解法

依次更新起始索引,从该位置开始找当前不重复子串最长长度,代码实现如下:

public int lengthOfLongestSubstring(String s) {
    if (s == null || s.length() < 1) {
        return 0;
    }
    int maxLen = 0;
    for (int begin = 0; begin < s.length(); begin++) {
        // 从begin开始往后找不重复字符最长长度
        Set<Character> charSet = new HashSet<>();
        int currLen = 0;
        for (int end = begin; end < s.length() && !charSet.contains(s.charAt(end)); end++) {
            // end字符没有重复,更新长度
            currLen++;
            // 记录当前字符
            charSet.add(s.charAt(end));
        }
        // 判断是否需要更新子串最长长度
        maxLen = Math.max(maxLen, currLen);
    }
    return maxLen;
}

时间复杂度O(n^2),空间复杂度O(A),其中A是字符集大小,因为需要Hash集合存储所有出现过的字符,所以最多需要字符集大小的空间。

 3. 滑动窗口

记录上一次判断的结束索引位置end,每次更新起始位置后,先将上一个起始位置字符从已出现过字符集中去除,然后直接从end继续向后判断。代码实现如下:

public int lengthOfLongestSubstring(String s) {
    if (s == null || s.length() < 1) {
        return 0;
    }
    Set<Character> charSet = new HashSet<>();
    int maxLen = 0;
    // 认为begin - end间是检查过的字符,初始为-1表示尚未开始检查
    int end = -1;
    for (int begin = 0; begin < s.length(); begin++) {
        if (begin > 0) {
            // 如果存在上一个起始节点,后移时把上个字符去除
            charSet.remove(s.charAt(begin - 1));
        }
        while ((end + 1 < s.length()) && !charSet.contains(s.charAt(end + 1))) {
            // end + 1与前面字符不重复,添加进集合
            charSet.add(s.charAt(end + 1));
            end++;
        }
        // 判断是否需要更新子串最长长度
        maxLen = Math.max(maxLen, end - begin + 1);
    }
    return maxLen; 
}

注意代码中处理end的方式,定义了end初始化值为-1,每次处理从end后一个索引检查,判断是否是重复字符,这样可以避免begin后移后,添加begin所在字符的逻辑在begin这个分支上,统一让end指示当前需要添加和判断的字符。

可以考虑两个边界场景:

1. begin = 0

此时需要把begin对应的字符添加到字符集,如果end = 0,那么在该场景需要特殊处理,把end对应的字符放入字符集,因为一般场景下,是顺序后移end,不需要考虑end对应索引。但是当初始end = -1时,可以保持处理逻辑与一般场景一致,只需要考虑end + 1索引对应的字符是否重复即可。

2. begin = end

与上述场景一样,判断end + 1索引位置可以统一边界场景。

时间复杂度O(n):因为begin和end都只遍历了一遍字符串。

空间复杂度O(A):其中A为字符集大小,因为需要Hash集合保存非重复字符,字符最大个数为字符集大小。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值