LeetCode无重复字符的最长子串Java(最容易理解的滑动窗口回溯过程)

题目要求:
给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。
示例 1:
输入: “abcabcbb”
输出: 3
解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。
示例 2:
输入: “bbbbb”
输出: 1
解释: 因为无重复字符的最长子串是 “b”,所以其长度为 1。
示例 3:
输入: “pwwkew”
输出: 3
解释: 因为无重复字符的最长子串是 “wke”,所以其长度为 3。
请注意,你的答案必须是 子串 的长度,“pwke” 是一个子序列,不是子串。

题目要求相当于一个字符串中最长的不重复子串,并且子串必须连续。

我的解法,暴力解法:
思路:首先,看到不重复,首先想到散列表,散列表在Java中的实现之一,就是HashSet,所以,我打算用HashSet。我的想法是,把字符串从头到尾一个个字符遍历,每遍历一个就添加进一个Set中,然后对比添加前和添加后,Set的大小是否改变,若改变,说明添加的字符是不重复的,若没改变,说明此字符已经存在于Set中,说明此时以第一个字符起始的位置出现了一个最长不重复的长串,记录下此串长度。此时,从第一个字符开始的最长串已经被找到,然后从第二个字符开始,依次循环以上方法。把每个字符起始的长串都找到,并且保存下所有长串的长度,最后找出最大长度即可。

class Solution {
    public int lengthOfLongestSubstring(String s) {

        HashSet<Character> subStr=new HashSet<>();//用来判断是否重复的Set
        HashSet<Integer> leng=new HashSet<>();//用来保存所有串的长度
        for(int i=0,j=0;i<s.length();i++){
            int frontSize=subStr.size();//把一个字符添加进Set前,Set的长度
            subStr.add(s.charAt(i));//把一个字符添加进set
            int backSize=subStr.size();//添加后set的长度
            if(frontSize==backSize){//判断添加前后是否相等,来判断前面是否是最长不重复串
                leng.add(backSize);//若相等,保存最长不重复串长度
                i=j;//j的作用,就是当第一个字符找到最长不重复串后,使得循环从第二个字符开始继续
                j++;//j初始为0,不管i此时为几,都把i赋值为0,然后i++后从1开始循环,然后j++
                subStr.clear();//若已经找到一个长串,再次从第二个字符循环时,将set清空
            }
        }
         leng.add(subStr.size());//最后肯定剩一个没有重复的串,将其长度添加
         Iterator<Integer> itr = leng.iterator();
         int max=0;
        while (itr.hasNext()) {//遍历长度,找出最长的
            int aaa=itr.next();
            if(max<aaa){
                max=aaa;
            }
         }
         return max;
    }
}

我在实现此算法时掉进的一些坑,估计大家也会遇到:
1.是j的问题,我刚开始把j赋值为1,当出现重复时,此时需要回溯到第二个字符运行时,将j赋值给i后,i=1,然后,for循环中还有个i++,i就变为2了,相当于跳了两格。此问题是忽略了for循环中本身的i++代码导致每次想前进一个字符,实际却跳了两格。
2.最后会剩下一个不重复的串,比如“aab”,a和a重复,记录下a,然后,从第二个a遍历时,后面b没有重复,主串遍历完了,循环结束,此时,最后会剩下一个ab是不重复的,需要记录下最后一个不重复的串,这一点很容易被忽略。


最优解法,滑动窗口:
仔细观察题目,就会发现,暴力解法其实每次都是依次回溯到第二个位置…第三个位置…等等,其实完全没有必要,只需要回溯到出现重复的字符的下一个位置即可。比如abcdefgchijkl,当遍历到第二个c时,c出现重复,此时,不需要回溯到b,可以直接跳到第一个c后面执行。
☆☆☆☆☆因为,回溯到第二个字符,从b继续遍历的话,到第二个c时,c仍然会重复,并且,从b开始的长度肯定小于从a开始的长度,因此,只有跳过第一个c,后面才有可能会出现长度大于从a开始的长度,因此可以直接大胆跳过第一个重复的字符,从它后面一位继续开始。 之前的暴力解法,用HashSet来处理重复的问题,但是,这里需要继续滑动窗口的位置,因此,升级一下,用HashMap,既能表示重复的问题,还能用value来记录每个字符的位置,方便滑动窗口的移动。
代码:

class Solution {
    public int lengthOfLongestSubstring(String s) {
        int n = s.length(), ans = 0;
        Map<Character, Integer> map = new HashMap<>();
        for (int end = 0, start = 0; end < n; end++) {
            char alpha = s.charAt(end);
            if (map.containsKey(alpha)) {
                start = Math.max(map.get(alpha), start);
            }
            ans = Math.max(ans, end - start + 1);
            map.put(s.charAt(end), end + 1);
        }
        return ans;
    }
}

最后再总结一下学习滑动窗口来解决问题时的主要理解障碍:
我的理解障碍,主要在于滑动窗口到底怎么移动的问题?为什么能直接跳到第一个重复字符的后面执行,看了很多讲解,其实都没用讲清楚,最后还是我自己想明白了,想必大家肯定刚开始也有和我一样的困惑,所以再次举例详细解释一下,就是前面解释的那个例子:
比如abcdefgchijkl,当遍历到第二个c时,c出现重复,此时,不需要回溯到b,可以直接跳到第一个c后面执行。
☆☆☆☆☆因为,回溯到第二个字符,从b继续遍历的话,到第二个c时,c仍然会重复,并且,从b开始的长度肯定小于从a开始的长度,因此,只有跳过第一个c,后面才有可能会出现长度大于从a开始的长度,因此可以直接大胆跳过第一个重复的字符,从它后面一位继续开始。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值