每日一恋 - LeetCode 3. Longest Substring Without Repeating Characters(无重复字符的最长子串)

题目描述:

Given a string, find the length of the longest substring without repeating characters.

Examples:

Given abcabcbb, the answer is abc, which the length is 3.

Given bbbbb, the answer is b, with the length of 1.

Given pwwkew, the answer is wke, with the length of 3. Note that the answer must be a substring, pwke is a subsequence and not a substring.

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

示例:

给定 abcabcbb ,没有重复字符的最长子串是 abc ,那么长度就是3。

给定 bbbbb ,最长的子串就是 b ,长度是1。

给定 pwwkew ,最长子串是 wke ,长度是3。请注意答案必须是一个子串,pwke 是 子序列 而不是子串。

解法一(Brute Force):

暴力解法,枚举所有的子串,使用两层循环,外层 i 为 [0…n-1],内层 j 为 [i+1…n]。接下来就是这么判断子串中是否有重复的元素,我们可以使用 Set 集合数据结构,一旦发现遍历的字符,集合中已经存在,则返回 false 即可。

public int lengthOfLongestSubstring(String s) {
    int n = s.length();
    int max = 0;
    for ( int i = 0 ; i < n - 1; i ++ ) {
        for ( int j = i + 1 ; j < n ; j ++ ) {
            if (allUnique(s, i, j))
                max = Math.max(max, j - i + 1);
        }
    }
    return max;
}

public boolean allUnique(String s, int start, int end) {
    // [start...end]
    HashSet<Character> set = new HashSet<>();
    for (int i = start ; i <= end ; i ++) {
        char c = s.charAt(i);
        if (set.contains(c))
            return false;
        set.add(c);
    }
    return true;
}

时间复杂度: O(n3) O ( n 3 ) ,为了检查字符是否在 [i,j) [ i , j ) ,需要扫描它们,时间复杂度 O(ji) O ( j − i )
空间复杂度: O(min(n,m)) O ( m i n ( n , m ) ) ,我们需要 O(k) O ( k ) 的空间来检查子串中是否含有重复元素,k由Set的大小决定。n为Set集合大小的上界,m为字符集大小/字母数量。

解法二(Sliding Window):

解法一对子串做了许多重复的检查,有一大部分是不需要的。比如子串 sij s i j i i j1 已经确认没有重复的字符,那么我们只需要判断 s[j] s [ j ] 在子串 sij s i j 中存在。为了检查字符是否已经包含在子串中,我们扫描了子串,导致时间复杂度 O(n2) O ( n 2 )

如果使用HashSet作为滑动窗口,就可以使时间复杂度降为 O(1) O ( 1 ) 。所以我们可以使用HashSet储存当前窗口的字符 [i,j)(j=i [ i , j ) ( j = i 初始时),j向右滑动窗口,如果不在HashSet中,就继续滑动j,直到 s[j] s [ j ] 已经在HashSet中了,这样我们就找到了以 i i 开始最长的没有重复字符的子串,一直遍历所有的i,就可以得到答案。

public int lengthOfLongestSubstring(String s) {
    // [i,j)
    int n = s.length();
    Set<Character> set = new HashSet<>();
    int max = 0;
    int i = 0;
    int j = 0;
    while (i < n && j < n) {

        if (!set.contains(s.charAt(j))) {
            set.add(s.charAt(j++));
            max = Math.max(max, j - i );
        }
        else {
            set.remove(s.charAt(i++));
        }
    }
    return max;
}

时间复杂度: O(2n)=O(n) O ( 2 n ) = O ( n ) ,最坏的情况是每个字符要遍历两次。
空间复杂度: O(min(n,m)) O ( m i n ( n , m ) ) ,我们需要 O(k) O ( k ) 的空间来检查子串中是否含有重复元素,k由Set的大小决定。n为Set集合大小的上界,m为字符集大小/字母数量。

解法三(Sliding Window Optimized):

解法二的优化,解法二中最多需要 2n 步,实际上并不需要那么多,当在确认右边界处的字符在集合中已经存在,那么不是一步一步地向前推进左边界到重复元素处,而是立即跳过包括重复元素在内之前的元素。

public int lengthOfLongestSubstring(String s) {

    // 优化:map 的value保存的是字符在s中的索引位置
    // 假如s[j] 与 s[i...j)中有重复元素j'
    // 更新 l 可以跳过 [l...j'] 范围内的所有元素,并直接将 l 变为 j' + 1
    Map<Character, Integer> map = new HashMap<>();
    int l = 0;
    int r = 0;
    int max = 0;

    while ( l < s.length() && r < s.length()) {

        if (!map.containsKey(s.charAt(r))) {
            map.put(s.charAt(r++), r); // without repeat, move r everytime
            max = Math.max( max, r-l );
        }
        else {
            Integer remove = map.remove(s.charAt(l)); // remove repeat character
            l = remove + 1;
        }

    }
    return max;
}

如果事先已经知道字符串中的字符种类很少,那么就可以将 Map 替换为整形数组直接访问,常见的数组为:

  • int[26] for Letters ‘a’ - ‘z’ or ‘A’ - ‘Z’
  • int[128] for ASCII
  • int[256] for Extended ASCII
public int lengthOfLongestSubstring(String s) {

    int[] freq = new int[256];
    int l = 0;
    int r = 0;
    int max = 0;

    while ( l < s.length() && r < s.length()) {

        if (freq[s.charAt(r)] == 0) {
            freq[s.charAt(r++)] ++;
            max = Math.max( max, r-l );
        }
        else {
            freq[s.charAt(l++)] --;
        }
    }

    return max;
}

时间复杂度: O(n) O ( n ) ,索引 j 将会迭代 n 次。
空间复杂度(HashMap): O(min(m,n)) O ( m i n ( m , n ) )
空间复杂度(Table): O(m) O ( m ) ,m是字符集的大小。

解法四:

还有一种思路是设置一个counter计数变量,计算当前窗口中重复的元素:如果没有重复的元素,右指针就依次向右移动,并记录最大不重复子串的长度;如果有重复的元素(counter > 0),那么就向右移动左指针,并判断当前元素是否是重复的元素,如果是那么counter将相应的减一,如果不是则直接移动左指针,直到counter等于0。

public int lengthOfLongestSubstring(String s) {

    int[] hash = new int[256];
    int l = 0;
    int r = 0;
    int max = 0;
    int counter = 0; // 重复元素数量

    while ( r < s.length() ) {

        if ( hash[s.charAt(r++)] ++ > 0 )
            counter++;

        while (counter > 0)  // 找到重复元素,右移左边界
            if (hash[s.charAt(l++)] -- > 1)
                counter --; // 变为可用

        max = Math.max( max, r-l );
    }

    return max;
}

代码都是可以直接运行的,希望能帮到一些人,如果文章里有说得不对的地方请前辈多多指正~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
根据提供的引用内容,有三种方法可以解决LeetCode上的最长回文子串问题。 方法一是使用扩展中心法优化,即从左向右遍历字符串,找到连续相同字符组成的子串作为扩展中心,然后从该中心向左右扩展,找到最长的回文子串。这个方法的时间复杂度为O(n²)。\[1\] 方法二是直接循环字符串,判断子串是否是回文子串,然后得到最长回文子串。这个方法的时间复杂度为O(n³),效率较低。\[2\] 方法三是双层for循环遍历所有子串可能,然后再对比是否反向和正向是一样的。这个方法的时间复杂度也为O(n³),效率较低。\[3\] 综上所述,方法一是解决LeetCode最长回文子串问题的最优解法。 #### 引用[.reference_title] - *1* [LeetCode_5_最长回文子串](https://blog.csdn.net/qq_38975553/article/details/109222153)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [Leetcode-最长回文子串](https://blog.csdn.net/duffon_ze/article/details/86691293)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [LeetCode 第5题:最长回文子串(Python3解法)](https://blog.csdn.net/weixin_43490422/article/details/126479629)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值