无重复字符的最长子串

无重复字符的最长子串
给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。
示例 1:
输入: s = "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
作者:力扣 (LeetCode)
链接:https://leetcode-cn.com/leetbook/read/top-interview-questions-medium/xv2kgi/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

以下是最初比较复杂的想法(是一些废话)

首先看到这个问题我想到的是通过一个map存储每个元素最近一次出现的位置。两个指针解决,一个指针i表示子串的开始,一个指针j表示子串的结尾的下一个元素用以判断重复元素。
当j指向的字符不是重复元素(即在map中未出现)时j前移将j位置入map。当j指向重复元素时,记录一下i到j-1的长度。然后i跳至j元素最近一次出现的位置的下一位。
但是这个想法有几个问题:
1.对于aaa这样的子串,最长的长度就是1,i,j会指向一个元素,那么记录i与j-1的长度(-1)是不合理的
2.对于abcbaef最长子串是cbaef,j遍历到b时由于重复元素的缘故,i应该跳到b最近一次出现的位置的下一位也就是c,接着j继续遍历到a发现a已经出现过,在进行跳转反而会小于当前位置。这时逻辑就出现了问题。实际上重现遍历的子串出现的字母应该与之前出现的字母无关。也就是i移动时应该在map中删除路上出现的字符。
注:上条问题倒是可以用j = Math.max(j, map.get(s.charAt(i)) + 1);解决
其实,这道题的问题还是自己的逻辑没有缕清,如果j指向的是当前最长子串的下一位,那么i,j就不应该相遇。但是如果让j指向i的下一位又需要增串长度的边界条件。而面对aaa这种串i进行跳转的时候还会和j相遇,那么又要对ij相遇的情况做判断...总之,一开始思路不正确,那么就会越想越混乱把自己也绕糊涂了。

因此,还是要缕清思路,明确每个变量和逻辑,并保证逻辑不会冲突(其实这个时候我已经绕乱了,直接看的大神题解)

1.依然使用双指针i,j。j指向当前不重复子串的最后一位。从0开始循环到串的最后1位。
2.每次j都会标记当前字符为已访问,因为j是不重复子串最后一位,所以可以计算i到j的子串长度。
3.重点来了,大神算法的最精巧地方,若j指向的元素已经访问过进入循环,i向前移动,消去路上所有元素访问过的记号,也就是说,i会把当前位置到j最近一次出现位置上的元素全部置为未访问,然后指向下一个位置。j指向的元素也会被置为未访问,这是j仍然是新串的最后一个元素。重复2操作。
4.做标记的数据结构可以是一个容纳全部可能出现的字符的数组,也可以是一个Map<Character, Boolean>

class Solution {
    public int lengthOfLongestSubstring(String s) {
        int res = 0;
        int i = 0,j = 0;
        boolean[] seen = new boolean[256]; //测试数据字符不会多于扩展后的ASCII码(2^8 = 256)
        while(j < s.length()){
            while(seen[s.charAt(j)]){
                seen[s.charAt(i)] = false;
                i++;
            }
            seen[s.charAt(j)] = true;
            res = Math.max(res, j - i + 1);
            j++;
        }
        return res;

    }
}
接着,可以再换一种思路。

如果发现重复元素,那么这个元素最后一次出现位置元素与之前的元素对于新子串来说都可以不考虑。既然是之前的元素不考虑,那么就满足了队列的性质。
也就是说队列提供了暂存子串的功能,只需要每次判断队列的长度即可。

public int lengthOfLongestSubstring(String s) {
        Queue<Character> queue = new LinkedList<>();
        char[] array = s.toCharArray();
        int res = 0;
        for(char c : array){
            while(queue.contains(c)){ //只要队列包括当前字符就进行退队
                queue.poll();
            }
            queue.offer(c);
            res = Math.max(res, queue.size());
        }
        return res;
    }

所以说做题之前还是要完完整整想清楚逻辑,不然遇到问题会越做越乱。

posted @ 2021-10-23 16:56  芝芝与梅梅  阅读(67)  评论(0)    收藏  举报