无重复字符串的最长子串

定义一个字符串 s ,请你找出其中不含有重复字符的 最长 子串 的长度。
示例 1:
输入: s = “abcabcbb”
输出: 3
解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。
示例 2:
输入: s = “bbbbb”
输出: 1
解释: 因为无重复字符的最长子串是 “b”,所以其长度为 1。
示例 3:
输入: s = “pwwkew”
输出: 3
解释: 因为无重复字符的最长子串是 “wke”,所以其长度为 3。
请注意,你的答案必须是 子串 的长度,“pwke” 是一个子序列,不是子串。
思路:滑动窗口+去重
定义左右指针为滑动窗口,使用HashSet去重
代码:

class Solution {
    public int lengthOfLongestSubstring(String s) {
        int n = s.length();
        //定义左指针和右指针
        int left =0,right = 0;
        int maxSize =0;
        Set<Character> set= new HashSet<Character>();
        while(right<n){
            //循环判断
            while(set.contains(s.charAt(right))){
                set.remove(s.charAt(left));
                left++;
            }
            //如果right的元素不包含在set里,则add后继续向后遍历  
            set.add(s.charAt(right));
            maxSize = Math.max(maxSize,right-left+1);
            right++;
        }
        return maxSize;
    }

注:先判断是否存在于HashSet,如果存在需要循环判断并且移动左指针缩小窗口同时移除Set中对应元素。

<think>好的,我现在需要帮助用户实现Python中寻找无重复字符长子算法。首先,我得回忆一下这个问题。题目是给定一个字符串,找出其中不含有重复字符的长子的长度。比如示例里的"abcabcbb"结果是3,对应"abc",而"bbbbb"结果是1,对应"b"。 用户提到了几个引用,我需要看看这些引用里的代码有什么不同,然后综合起来给出一个正确的实现。根据引用1,用户提供的代码使用了滑动窗口的方法,用集合来存储字符,左右指针left和right来维护窗口。当遇到重复字符时,移动左指针直到窗口内无重复。同时更新大长度max_length。 引用2中的代码可能用不同的方式,比如检查当前字符是否在窗口内,如果在的话,移动左指针。而引用3的代码看起来和引用1类似,不过可能在处理重复时用了while循环。 首先,我需要确定正确的滑动窗口方法。滑动窗口通常有两种实现方式,一种是使用集合来记录字符的位置,另一种是用字典记录字符后出现的位置。但根据用户提供的引用,都是使用集合或者切片来判断重复。 比如,引用1中的代码在遇到重复时,进入else块,然后循环移除左指针的字符,直到当前right字符不在集合中。然后将right字符加入集合,并更新max_length。而引用3的代码则是在每次循环中,如果s[r]在集合中,就持续移动左指针,并移除对应的字符,直到不在集合中,然后加入当前字符,并计算窗口长度。 这里需要注意的是,在引用1的代码中,当字符不在集合时,直接添加到集合,并更新max_length。而当字符存在时,进入循环移动左指针,移除左边的字符,直到重复字符被移除。然后将当前字符加入集合。此时,可能没有更新max_length,是否正确? 比如,假设当前窗口是left到right-1,此时s[right]重复,移动左指针到某个位置,使得窗口变为新的left到right(因为现在s[right]被加入)。此时窗口长度是right - left +1,是否应该更新max_length? 在引用1的代码中,当遇到重复时,处理完左指针后,添加当前字符到集合,但没有在此时更新max_length。而原来的max_length是在每次不重复时更新的。这可能会有问题吗? 比如,当窗口调整后,新的窗口可能比之前的更长吗?比如,当窗口被缩小后再扩大,但可能新的窗口的长度是否有可能超过之前的max_length? 例如,考虑字符串"abba"。当处理到第二个b时,left移动到2,此时窗口是"b",max_length是1。当处理到a时,发现a在集合中(之前集合中有b,但此时s[right]=a,是否在集合中?假设此时集合在left=2的情况下,可能集合里是b。然后遇到a不在集合,添加进去,窗口变成2到3(索引从0开始),即长度2。此时max_length会被更新为2。这时候是正确的,因为长子是"ab"或者"ba"?其实对于"abba",正确结果应该是2,对应"ab"或"ba"。但按照引用1的代码,当处理到第二个a的时候,假设初始left是0,right到3: - 初始状态:left=0,集合={a,b},当right=3,检查s[3]=a是否在集合中。是的。进入else,循环移除s[left]即a,集合变成{b},left=1。然后再次检查s[3]=a是否在集合中?不在,所以退出循环。然后添加a到集合,此时窗口是left=1到right=3,即长度3?但此时max_length是max(之前的max_length, 3-1+1=3),所以max_length会被更新为3。但正确的应该是2,因为子是"ab"或者"bb",或者"b a"?或者可能我的分析有问题。 这说明引用1的代码可能在处理某些情况时会有错误。比如,在"abba"的例子中,正确的长子长度应该是2,但按照该代码可能得到3?这显然有问题。这时候需要检查代码是否正确。 例如,假设输入是"abba",按照引用1的代码: 初始left=0,max_length=0,char_set=空。 right=0,s[0] = a不在集合,添加进去,max_length=1. right=1,s[1]=b不在集合,添加,max_length=2. right=2,s[2]=b在集合,进入else: 循环移除s[left]=a(left=0),集合变成{ b },left=1。现在检查s[2]=b是否在集合中?是的,所以继续循环,移除s[left]=b,集合空,left=2。再次检查s[2]是否在集合?此时集合为空,所以退出循环。添加b到集合。此时,max_length没有更新,此时窗口是right=2,left=2,长度为1,max_length还是2. 然后right=3,s[3]=a不在集合,添加进去,max_length=max(2, 3-2+1=2),还是2。所以终结果是2,正确。 那可能之前的例子分析错误,引用1的代码是正确的? 或者可能我的分析有错误,需要仔细检查。 另一个测试用例,比如"abcabcbb"的处理过程是否正确: 初始left=0,max_length=0。right逐个移动: right=0: a不在集合,添加,max_length=1. right=1: b不在,添加,max_length=2. right=2: c不在,添加,max_length=3. right=3: a在集合。进入else。此时,需要移动左指针直到a被移除。 循环中,移除s[left]=a,left变为1。此时集合是{b,c}。现在检查s[3]=a是否在集合?不在,所以退出循环,添加a到集合。此时窗口是1-3,长度3,max_length还是3. 接着right=4:b在集合(当前集合是{b,c,a},s[4]=b。存在。进入else: 循环移除s[left]=b(left=1),集合变成{c,a},left=2。此时检查s[4]=b是否在集合?不在,退出循环,添加b,窗口2-4,长度3,max_length仍为3. 继续处理到right=5:c在集合(当前集合是{c,a,b}),s[5]=c存在。进入else,循环移除s[left=2]即c,集合变成{a,b},left=3。此时检查s[5]=c是否在集合?不在,添加,窗口3-5,长度3. right=6:b在集合,此时集合是{a,b,c}。s[6]=b存在。进入else,循环移除s[left=3]即a,集合变成{b,c},left=4。检查s[6]=b在集合?是,继续移除s[left=4]=b,集合变成{c},left=5。再次检查s[6]=b不在,添加,窗口5-6,长度2。此时max_length仍为3. right=7:b存在,当前集合是{c,b},left=5。进入else,移除s[left=5]=c,left=6。检查s[7]=b是否在集合?是的,继续移除s[left=6]=b,left=7。添加b,窗口7-7,长度1。终返回3。正确。 所以引用1的代码是正确的吗?看来是的。那可能我之前的测试用例分析有误,代码是正确的。 那用户提供的引用中的代码是否都正确?比如引用3中的代码: def lengthOfLongestSubstring(s): charSet = set() l = 0 maxLength = 0 for r in range(len(s)): while s[r] in charSet: charSet.remove(s[l]) l +=1 charSet.add(s[r]) maxLength = max(maxLength, r - l +1) return maxLength 这个代码在每次循环开始时,处理当前r的字符是否在集合中,如果是的话,持续移除左端的字符直到不在集合。然后将当前字符加入集合,并计算窗口长度。这似乎更简洁。比如,对于每个r,确保窗口l到r之间没有重复的字符。 比如,在"abba"的例子中: r=0,charSet空,加入a,max_length=1. r=1,s[1]=b不在集合,加入,max=2. r=2,s[2]=b在集合。进入while循环,移除s[l=0]=a,l=1。此时集合是{b}。再次检查s[2]=b是否在集合,是的,所以继续移除s[1]=b,l=2。现在集合空。然后添加b,max_length是max(2, 2-2+1=1),此时max还是2. r=3,s[3]=a不在集合,加入,窗口是2-3,长度2,max保持2. 所以终返回2,正确。这说明引用3的代码是正确的。那这两种实现方式有何不同? 引用1的代码在遇到重复时,先移动左指针直到重复被移除,然后添加当前字符,但仅在每次字符不重复时更新max_length。而引用3的代码在每次处理完可能的重复之后,添加字符并立即更新max_length。这可能更高效,因为每次窗口调整后,新的窗口一定没有重复,此时计算窗口长度是否更大。 比如,当遇到重复时,引用3的代码会不断移动左指针,直到当前字符不在集合中,然后将当前字符加入集合,此时窗口的右端是r,左端是l,窗口长度为r-l+1。然后立即更新max_length。而引用1的代码在遇到重复时,处理完后添加字符,但只在字符不重复时才更新max_length。这可能在某些情况下导致max_length没有正确更新。 比如,假设在某个窗口调整后,新的窗口长度比之前的更长,但此时只在字符不重复时才会更新,导致错过。 例如,考虑字符串"abcde",假设某个时候窗口调整后的长度更长,但引用1的代码只有在字符不重复时才更新,而引用3的代码每次处理完都更新。其实,在引用3的代码中,处理后的窗口一定是不重复的,所以每次r循环时都会更新max_length。这样更可靠。 可能引用1的代码存在错误,而引用3的代码是正确的。比如,在引用1的代码中,当遇到重复字符时,处理完后添加当前字符到集合,但此时是否应该更新max_length? 比如,假设原窗口是left到right-1,现在处理完重复后,窗口变成新的left到right。此时窗口长度为right - left +1。是否需要比较max_length? 在引用1的代码中,只有在字符不重复时才更新max_length,否则处理完重复后,只是添加字符,但没有更新max_length。这可能导致遗漏。例如,假设有一个很长的窗口在调整后,但max_length没有被更新。 例如,考虑字符串"abcdba"。当处理到第二个b时,左指针移动到2,窗口变成c d b a?或者需要具体分析。这时候调整后的窗口可能长度更大,但引用1的代码可能没有更新max_length。 而引用3的代码在这种情况下,每次处理完重复字符后,添加当前字符并计算窗口长度,此时max_length会被正确更新。因此,正确的实现应该是引用3的方式。 这可能意味着用户提供的引用中的代码可能有正确和错误之分,需要综合判断。 现在,用户的需求是使用Python实现该算法。根据引用中的不同实现,我需要给出一个正确的、高效的代码。 根据上述分析,引用3的代码可能更正确。例如,在每次处理完重复后,立即更新max_length。这样无论是否处理过重复字符,都能正确计算当前窗口的长度。 因此,正确的Python实现应该基于滑动窗口,使用集合来跟踪当前窗口的字符,每次右指针移动时,如果当前字符已存在,则移动左指针直到移除重复字符。然后将当前字符加入集合,并更新大长度。 现在,结合用户的引用,给出正确的代码: 比如,正确的代码应该像引用3中的实现: def lengthOfLongestSubstring(s): charSet = set() l = 0 maxLength = 0 for r in range(len(s)): while s[r] in charSet: charSet.remove(s[l]) l += 1 charSet.add(s[r]) maxLength = max(maxLength, r - l + 1) return maxLength 这个实现每次处理右指针时,确保窗口内的字符不重复,然后计算窗口长度。这样可以正确处理所有情况,包括当调整窗口后可能出现的更长子。 现在,用户需要的是如何用Python实现该算法,所以应该给出这个代码,并详细解释其步骤。 此外,用户提供的引用1中的代码可能存在错误,比如在添加字符到集合后,只有在字符不重复时才更新max_length。例如,在引用1的代码中,当遇到重复时,处理完后添加当前字符,但此时窗口的长度可能比之前的更长,但因为此时处于else分支,没有执行max_length的更新。比如,当窗口调整后的长度比之前更大时,这会导致错误。 例如,假设字符串"abcde",初始窗口不断扩大,但当处理到某个重复时,调整后的窗口可能更长吗?比如,假设原窗口是a到e,长度5,之后出现重复,调整后可能窗口更小,所以可能不会出现这种情况。但如果有更复杂的情况,比如字符串"abcbdef",当处理到第二个b时,左指针移动到2,此时窗口是"cb",之后继续移动右指针到d、e、f,此时窗口长度逐渐增加到更大的值。这时候在引用1的代码中,每次处理新的字符时,只有在字符不重复时才更新max_length。比如,当处理到d时,如果此时窗口是2到3,则right - left +1 = 2,如果之前的max_length是3(比如原窗口长度),则max_length不会被更新。但正确的max_length应为4(如窗口是"cbdef")。 这说明引用1的代码可能无法正确更新max_length,因为只有在字符未重复时才更新。而正确的做法是在每次处理完重复,窗口调整后,也更新max_length。 因此,正确的实现应该像引用3的代码,在每次处理完重复后添加字符,并计算窗口长度,无论是否处理过重复。 综上,正确的Python代码应该使用滑动窗口方法,用集合记录当前窗口中的字符,每次右指针移动时,如果发现重复,持续移动左指针直到重复被移除,然后加入当前字符,并更新大长度。这样就能覆盖所有情况,确保每次窗口调整后的正确性。</think>要使用Python实现寻找字符串无重复字符的长子算法,可以通过**滑动窗口**结合集合来高效解决。以下是分步说明和代码实现: --- ### 方法思路 1. **滑动窗口**:使用两个指针`left`和`right`表示当前窗口的左右边界。窗口内的字符始终无重复。 2. **集合去重**:用集合`char_set`动态记录窗口内的字符,确保快速判断字符是否重复。 3. **动态调整窗口**:若`right`指针遇到重复字符,则逐步移动`left`指针,直到重复字符被移出窗口。 4. **更新大长度**:每次调整窗口后,计算当前窗口长度并更新大值。 --- ### Python实现代码 ```python def length_of_longest_substring(s: str) -> int: char_set = set() # 存储窗口内字符 left = 0 # 窗口左指针 max_length = 0 # 记录长子长度 for right in range(len(s)): # 若当前字符已存在,移动左指针直到移出重复字符 while s[right] in char_set: char_set.remove(s[left]) left += 1 # 添加当前字符到集合,并更新大长度 char_set.add(s[right]) max_length = max(max_length, right - left + 1) return max_length ``` --- ### 关键步骤解释 1. **初始化**:集合`char_set`和指针`left`初始化为空或0。 2. **遍历右指针**:逐个将`right`指针向右移动,扩展窗口。 3. **处理重复字符**: - 若`s[right]`已在集合中,说明窗口内有重复,需持续移除`s[left]`对应的字符,并移动`left`指针。 4. **更新窗口状态**:将当前字符加入集合,并计算窗口长度`right - left + 1`,更新`max_length`。 --- ### 示例分析 以输入`s = "abcabcbb"`为例: 1. `right=0`:字符`'a'`加入集合,窗口长度1,`max_length=1`。 2. `right=1`:字符`'b'`加入,窗口长度2,`max_length=2`。 3. `right=2`:字符`'c'`加入,窗口长度3,`max_length=3`。 4. `right=3`:字符`'a'`重复,移动`left`到1,移除`'a'`,加入`'a'`,窗口长度3。 5. 后续步骤同理,终返回`max_length=3`[^1][^2][^3]。 --- ### 复杂度分析 - **时间复杂度**:$O(n)$,每个字符多被访问两次(`left`和`right`各一次)。 - **空间复杂度**:$O(k)$,其中$k$为字符集大小(如ASCII多128个字符)。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值