滑动窗口-无重复字符的最长子串

无重复字符的最长子串

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

输入:字符串
输出:整型变量
思路:两个指针一前一后,最开始都在0位置,然后有值则快指针向右移动一位,判断是否存在过

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;
    }
}



二刷感悟:

(a)bcabcbb 开始的最长字符串为 (abc)abcbb;
以 a(b)cabcbb 开始的最长字符串为 a(bca)bcbb;
以 ab(c)abcbb 开始的最长字符串为 ab(cab)cbb;
以 abc(a)bcbb 开始的最长字符串为 abc(abc)bb;
以 abca(b)cbb 开始的最长字符串为 abca(bc)bb;
以 abcab(c)bb 开始的最长字符串为 abcab(cb)b;
以 abcabc(b)b 开始的最长字符串为 abcabc(b)b;
以 abcabcb(b) 开始的最长字符串为 abcabcb(b)

关于滑动窗口,什么时候移动?怎么移动?为什么可以这么移动?
什么时候移动?
当判断某一个子串中有重复的字符时,开始移动,
怎么移动?
如果当前字符的加入,导致子串重复了,那么子串的头取掉一个,继续判断是否加入的当前字符导致新子串重复
为什么可以这么移动?
因为根据上面的推理,以abcabcbb为例,依次递增子串的起始位置,结束位置也在递增,并且当一个子串的起始位置是k,结束位置是r,那么当起始位置为k+1的子串,从k+1一直到r的子串一定是无重复字符的
具体方法
使用HashSet存储子串的每一个字符,当左指针向右移动的时候,我们从哈希集合中移除一个字符,在右指针向右移动的时候,我们往HashSet中添加一个元素。

class Solution {
    public int lengthOfLongestSubstring(String s) {
        int len = s.length();
        Set<Character> set = new HashSet<>();
        int r = -1;
        int max = 0;
        for(int i = 0; i < len; i++){
            if(i != 0){
                set.remove(s.charAt(i - 1));
            }
            while(r + 1 < len && !set.contains(s.charAt(r + 1))){
                set.add(s.charAt(r + 1));
                r++;
            }
            max = Math.max(max, r - i + 1);
        }
        return max;
    }
}

每次都要移动起始位置,所以使用for遍历

使用哈希表可以每次都移动结束位置,这样就只需判断起始位置从什么时候开始就可以,也就是这一个 l = Math.max(map.get(s.charAt(i)) + 1, l);的关键

class Solution {
    public int lengthOfLongestSubstring(String s) {
        Map<Character,Integer> map = new HashMap<>();
        int l = 0;
        int num = 0;
        for(int i = 0; i < s.length(); i++){
            if(map.containsKey(s.charAt(i))){
                // l = Math.max(map.get(s.charAt(i)) + 1, l);
                while(l < map.get(s.charAt(i)) + 1){
                    map.remove(l);
                    l++;
                }
            }
            map.put(s.charAt(i), i);
            num = Math.max(num, i - l + 1);
        }
        return num;
    }
}

注意,如果每次都移动起始位置,则使用HashSet比较合适,因为不用知道具体的索引位置,每次把这个字符移除掉就行,然后再进行右边字符的添加
但是!!!对于每次都移动结束位置,每次遍历都需要将结束位置加入集合中,此时必须使用HashMap,因为如果当前元素加入之后,在原本的HashMap表中已经存在,则可以根据索引将起始位置移动到不发生重复的那一位,然后根据索引计算子串中的元素数量,此题得解!
思考:那么如果每次移动起始位置,可以使用HashMap吗???
yes,也是可以,但是非常慢

class Solution {
    public int lengthOfLongestSubstring(String s) {
        int len = s.length();
        Map<Integer,Character > map = new HashMap<>();
        int r = -1;
        int ans = 0;
        for(int i = 0; i < len; i++){
            if(i != 0){
                map.remove(i - 1);
            }
            while(r + 1 < len && !map.containsValue(s.charAt(r + 1))){
                map.put(r + 1, s.charAt(r + 1));
                r++;
            }
            ans = Math.max(ans, r + 1 - i);
        }
        return ans;
    }
}

在这里插入图片描述
可以将HashMap换成数组,操作起来比较方便,也更快

class Solution {
    public int lengthOfLongestSubstring(String s) {
        int len = s.length();
        int[] charIndex = new int[128]; // ASCII 字符范围
        Arrays.fill(charIndex, -1); // 初始化为 -1,表示字符未出现过
        int r = -1;
        int ans = 0;

        for (int i = 0; i < len; i++) {
            if (i != 0) {
                charIndex[s.charAt(i - 1)] = -1; // 移除左边界字符
            }
            while (r + 1 < len && charIndex[s.charAt(r + 1)] == -1) {
                charIndex[s.charAt(r + 1)] = r + 1; // 记录字符索引
                r++;
            }
            ans = Math.max(ans, r + 1 - i);
        }
        return ans;
    }
}

用-1来标记当前字符是否出现过

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值