「力扣」第 3 题:无重复字符的最长子串(滑动窗口、哈希表)

知识点:

  • 转换成字符数组,是字符串问题常见的处理方法。这是因为 charAt 方法,每次访问的时候都会做边界判断,在我们求解的问题中,是不必要的;
  • 「滑动窗口」的规则(这里要根据题目意思想出来):要求无重复字符的最长子串,那么需要关注的「状态」就是有重复元素;
  • 右边界 right 滑动到刚刚好有重复的时候停下,然后左边界 left 滑动到刚刚好没有重复的时候停下。

方法一:滑动窗口

Java 代码:

public class Solution {
    
    

    public int lengthOfLongestSubstring(String s) {
    
    
        int len = s.length();
        // 特判
        if (len < 2) {
    
    
            return len;
        }

        char[] charArray = s.toCharArray();

        // 当 window 中某个字符的频数为 2 时,表示滑动窗口内有重复字符
        int[] count = new int[128];
        // 循环不变量:保持不变的性质是:[left, right) 内没有重复元素
        int left = 0;
        int right = 0;
        // 滑动窗口内是否重复
        boolean repeating = false;
        int res = 1;

        while (right < len) {
    
    
            // 不能写在后面,因为数组下标容易越界
            if (count[charArray[right]] == 1) {
    
    
                repeating = true;
            }
            count[charArray[right]]++;
            right++;

            // 此时 [left, right) 内如果有重复元素,就缩小左边界,直到滑窗内没有重复元素
            // 否则,让 right 继续右移,这样不会错过最优解
            while (repeating) {
    
    
                if (count[charArray[left]] == 2) {
    
    
                    // 如果满足滑动窗口内有重复的元素,尝试不断删除左边元素
                    repeating = false;
                }
                // 只有有重复元素,就得缩短左边界
                count[charArray[left]]--;
                left++;
            }
            // 此时 [left, right) 内没有重复元素
            res = Math.max(res, right - left);
        }
        return res;
    }
}

方法二:滑动窗口 + 哈希表

思路:使用哈希表记录上一次的位置,左边界直接来到不重复的位置。

  • 非空的时候,结果至少是 1 ,因此初值 res 可以设置成为 1;

  • 右边界没有重复的时候,直接向右边扩张就好了;

  • 右边界有重复的时候,只要在滑动窗口内,我们就得更新;

  • 如果在滑动窗口之外,一定是之前被计算过的;

  • 下一个不重复的子串至少在之前重复的那个位置之后。

Java 代码:

import java.util.HashMap;
import java.util.Map;

public class Solution {
    
    

    public int lengthOfLongestSubstring(String s) {
    
    

        int len = s.length();
        if (len < 2) {
    
    
            return len;
        }

        char[] charArray = s.toCharArray();

        int left = 0;
        int right = 0;

        //  key 为字符,val 记录了当前读到的字符的下标,同样的字符保留最后的
        Map<Character, Integer> map = new HashMap<>(len);

        int res = 1;
        while (right < len){
    
    
            if (map.containsKey(charArray[right])) {
    
    
                // 注意这里是大于等于
                if (map.get(charArray[right]) >= left) {
    
    
                    // 注意:快在这个地方,左边界直接跳到之前重复的那个位置之后
                    left = map.get(charArray[right]) + 1;
                }
            }
            // 无论如何都更新位置
            map.put(charArray[right], right);
            right++;

            // 此时滑动窗口内一定没有重复元素
            res = Math.max(res, right - left);
        }
        return res;
    }
}

Java 代码:

public class Solution2 {
    
    

    // 也可以用数组代替滑动窗口

    public int lengthOfLongestSubstring(String s) {
    
    
        // 重复元素上一次出现的位置很重要
        int len = s.length();
        if (len < 2) {
    
    
            return len;
        }
        
        int[] window = new int[128];
        for (int i = 0; i < 128; i++) {
    
    
            window[i] = -1;
        }

        char[] charArray = s.toCharArray();

        int res = 1;
        int left = 0;
        for (int i = 0; i < len; i++) {
    
    
            if (window[charArray[i]] != -1) {
    
    
                left = Math.max(left, window[charArray[i]] + 1);
            }
            window[charArray[i]] = i;
            // 注意理解这里为什么是 + 1
            res = Math.max(res, i - left + 1);
        }
        return res;
    }
}

猜你喜欢

转载自blog.csdn.net/lw_power/article/details/105772566