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

本文提供了一种解决LeetCode第3题的方法:给定一个字符串,找出不含有重复字符的最长子串的长度。通过使用双指针i和j进行遍历,实现了对字符串的有效检查,最终找到最长的不含重复字符的子串长度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

public class LeetCode3 {
       public static void main(String[] args) {
        /**
         * LeetCode第3题:
         * 给定一个字符串,找出不含有重复字符的最长子串的长度。
         * 示例:给定 "abcabcbb" ,没有重复字符的最长子串是 "abc" ,那么长度就是3。
         * 解题思路:建立指针i,j,i=0,j=i+1;最大字符长度为max
         * if str[i-(j-1)]的区间中存在 = str[j] , i++;j=i+1;break;
         * else j++;
         * 运行时间 n的2次方
         */
           String s = "pwwkew";
           System.out.println(lengthOfLongestSubstring(s));
    }
       
       public static int lengthOfLongestSubstring(String s) {
           if(s.length() == 1) return 1;
           if(s.length() == 0) return 0;
           char str[] = s.toCharArray(); 
           int i = 0,j = i+1,max = 0;
           boolean  t = true;
           while(i < s.length() - 1){
                   int length = 1;
                   //检测是否重复
                   if(j == s.length()) break;
                   for(int k = i; k < j;k++){
                       if(str[j] != str[k] ){
                           length++;
                           }else{
                               t = false;
                                break;
                           }
                   }
                   if(length > max){
                       max = length; 
                   }
                   //如果检测成功,j指针右移一次
                   if(t) j++;
                   //检测失败,i指针右移一次,j指针回到i+1的位置
                   else{
                       t = true; i++; j = i + 1; length = 1;
                   }
           }
           return max;
       }
}

<think>我们使用滑动窗口(双指针)和哈希集合(unordered_set)来高效解决。 思路: 1. 使用两个指针(left和right)表示窗口的左右边界,初始都指向0。 2. 使用一个哈希集合(unordered_set)记录窗口中的字符,保证无重复。 3. 右指针断向右移动,将新字符加入窗口(集合): - 如果加入后没有重复,则更新长度。 - 如果有重复,则移动左指针,并从集合中移除左指针所指的字符,直到重复字符被移除(即窗口内再次无重复)。 4. 重复上述过程直到右指针到达字符串末尾。 注意:当遇到重复字符时,我们需要将左指针移动到重复字符的下一个位置,但注意重复字符可能在左边界,所以需要循环移除直到重复字符被移除。 例子: "abcabcbb" 初始:left=0, right=0, 窗口内为空,maxLen=0 右指针移动:窗口"a" -> 无重复,maxLen=1 继续:窗口"ab" -> 无重复,maxLen=2 继续:窗口"abc" -> 无重复,maxLen=3 继续:加入&#39;a&#39;,发现重复(因为集合中已有&#39;a&#39;),则移动左指针,移除窗口左边的&#39;a&#39;(即left从0移动到1),此时窗口为"bca",继续。 但是,上面的例子中,当加入第二个&#39;a&#39;时,重复字符是&#39;a&#39;,而集合中的&#39;a&#39;是第一个&#39;a&#39;,所以我们需要将左指针移动到第一个&#39;a&#39;的下一个位置(即0+1=1),这样窗口就从1开始。 因此,我们也可以用一个哈希表(unordered_map)来记录每个字符近一次出现的位置,这样当遇到重复时,我们可以快速将左指针移动到重复字符的下一个位置。但注意,左指针只能向右移动,能回退,所以需要取大值。 优化:使用哈希表记录字符的索引,这样在发现重复时,可以直接将左指针跳到max(left, 重复字符上次出现位置+1)。这样可以避免逐个移动左指针。 两种方法: 方法1:使用集合,逐个移动左指针(坏情况时间复杂度O(2n)) 方法2:使用映射(字符到索引),直接跳转左指针(坏情况时间复杂度O(n)) 这里我们采用方法2,更高效。 步骤: 初始化:left=0, maxLen=0 遍历right从0到n-1: 如果当前字符s[right]在哈希表中已经存在,并且其值(上次出现的位置)大于等于left(说明在窗口内),则更新left = map[s[right]] + 1。 更新哈希表:将s[right]的位置更新为right(即记录当前字符的新位置) 计算当前窗口长度:right-left+1,更新maxLen 注意:为什么是大于等于left?因为哈希表记录的是字符出现的位置,可能出现在left之前(即在当前窗口内),所以只有当重复字符的位置在left之后(即窗口内)才需要移动left。 但是,还有一种情况:如果重复字符的位置在left之前,那么当前窗口内并没有这个字符,所以需要移动left。因此,我们只需要考虑重复字符的位置在[left, right)之间。 例如:字符串 "abba" 当right=0: 字符&#39;a&#39;,记录map[&#39;a&#39;]=0,maxLen=1 right=1: 字符&#39;b&#39;,记录map[&#39;b&#39;]=1,maxLen=2 right=2: 字符&#39;b&#39;,此时map[&#39;b&#39;]=1(在left=0的后面,所以是窗口内重复),所以left更新为map[&#39;b&#39;]+1=2,然后更新map[&#39;b&#39;]=2,当前长度=2-2+1=1,maxLen=2 right=3: 字符&#39;a&#39;,此时map[&#39;a&#39;]=0(0<left=2,所以在当前窗口内),所以移动left,然后更新map[&#39;a&#39;]=3,当前长度=3-2+1=2,maxLen=2 因此,代码实现如下: 注意:我们使用一个哈希表记录每个字符近一次出现的位置(索引)。 时间复杂度:O(n),空间复杂度:O(字符集大小) 代码:</think># LeetCode 长无重复子长度 C++ 实现 我将为您提供两种高效的C++解法来解决"长无重复子"问,包括滑动窗口法和哈希表优化法,这两种方法都是LeetCode上推荐的解决方案。 ## 问描述 给定一个字符串找出其中含有重复字符的**长子**的长度。 示例: - 输入: "abcabcbb" → 输出: 3 ("abc") - 输入: "bbbbb" → 输出: 1 ("b") - 输入: "pwwkew" → 输出: 3 ("wke") ## 方法1: 滑动窗口法(推荐) ```cpp #include <iostream> #include <unordered_set> #include <algorithm> using namespace std; int lengthOfLongestSubstring(string s) { int n = s.length(); if (n <= 1) return n; unordered_set<char> window; // 存储当前窗口中的字符 int left = 0, maxLen = 0; for (int right = 0; right < n; right++) { // 当遇到重复字符时,移动左指针 while (window.find(s[right]) != window.end()) { window.erase(s[left]); // 移除左指针字符 left++; // 左指针右移 } window.insert(s[right]); // 添加当前字符到窗口 maxLen = max(maxLen, right - left + 1); // 更新长度 } return maxLen; } int main() { cout << lengthOfLongestSubstring("abcabcbb") << endl; // 输出: 3 cout << lengthOfLongestSubstring("bbbbb") << endl; // 输出: 1 cout << lengthOfLongestSubstring("pwwkew") << endl; // 输出: 3 return 0; } ``` **算法解析**: 1. 使用`left`和`right`指针表示滑动窗口的左右边界 2. 使用哈希集合`window`存储当前窗口中的字符 3. 遍历字符串: - 当遇到重复字符时,移动左指针并移除对应字符 - 添加当前字符到窗口 - 更新长度:$maxLen = \max(maxLen, right-left+1)$ 4. **时间复杂度**:$O(n)$(每个字符多被访问两次) 5. **空间复杂度**:$O(\min(m, n))$($m$为字符集大小) ## 方法2: 哈希表优化法(更高效) ```cpp #include <iostream> #include <unordered_map> #include <algorithm> using namespace std; int lengthOfLongestSubstringOptimized(string s) { int n = s.length(); if (n <= 1) return n; unordered_map<char, int> charIndex; // 存储字符后出现的位置 int left = 0, maxLen = 0; for (int right = 0; right < n; right++) { // 如果字符已存在且在当前窗口内,直接跳转左指针 if (charIndex.find(s[right]) != charIndex.end() && charIndex[s[right]] >= left) { left = charIndex[s[right]] + 1; // 跳过重复字符 } charIndex[s[right]] = right; // 更新字符新位置 maxLen = max(maxLen, right - left + 1); // 更新长度 } return maxLen; } ``` **优化点分析**: 1. 使用哈希表`charIndex`记录每个字符**后出现的位置** 2. 当遇到重复字符时,直接跳转左指针到重复字符的下一位 3. 避免方法1中的`while`循环,时间复杂度严格$O(n)$ 4. 空间复杂度仍为$O(\min(m, n))$ ## 关键算法思想 ### 滑动窗口原理 滑动窗口技术通过维护一个动态变化的窗口来解决问: 1. **窗口扩展**:右指针向右移动,扩大窗口 2. **窗口收缩**:当遇到重复字符时,左指针向右移动,缩小窗口 3. **窗口有效性**:窗口内始终保证无重复字符 ### 复杂度对比 | 方法 | 时间复杂度 | 空间复杂度 | 特点 | |--------------|------------|------------|--------------------| | 滑动窗口法 | $O(n)$ | $O(k)$ | 实现简单,逻辑清晰 | | 哈希表优化法 | $O(n)$ | $O(k)$ | 性能更优,跳转高效 | 其中$k$表示字符集大小,$n$表示字符串长度。 ## 测试用例分析 ```cpp void testLengthOfLongestSubstring() { // 基础测试 assert(lengthOfLongestSubstring("") == 0); assert(lengthOfLongestSubstring("a") == 1); // 标准测试 assert(lengthOfLongestSubstring("abcabcbb") == 3); assert(lengthOfLongestSubstring("bbbbb") == 1); assert(lengthOfLongestSubstring("pwwkew") == 3); // 边界测试 assert(lengthOfLongestSubstring("abcdefg") == 7); assert(lengthOfLongestSubstring("aab") == 2); assert(lengthOfLongestSubstring("dvdf") == 3); // 特殊字符测试 assert(lengthOfLongestSubstring("!@#$%^&*()") == 10); assert(lengthOfLongestSubstring("112233") == 2); } ``` ## 性能优化技巧 1. **字符集优化**:对于已知字符集(如仅小写字母),可使用固定大小数组代替哈希表 2. **提前终止**:当剩余长度小于当前长度时,可提前终止 3. **位图压缩**:对于ASCII字符,可使用位图进一步减少空间占用
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值