StringMinimumWindowSubstring76LeetCode给你一个字符串 S、一个字符串 T,请在字符串 S 里面找出:包含 T 所有字母的最小子串。

博客围绕LeetCode题目展开,要求在字符串S中找出包含字符串T所有字母的最小子串,聚焦于字符串处理和算法求解问题,属于信息技术领域的算法应用。
import java.util.HashMap;
/**
 * 给你一个字符串 S、一个字符串 T,请在字符串 S 里面找出:包含 T 所有字母的最小子串。
 * 示例:
 * 输入: S = "ADOBECODEBANC", T = "ABC"
 * 输出: "BANC"
 * 说明:
 * 如果 S 中不存这样的子串,则返回空字符串 ""。
 * 如果 S 中存在这样的子串,我们保证它是唯一的答案。
 * 思路:滑动窗口法
  在滑动窗口类型的问题中都会有两个指针。一个用于延伸现有窗口的 right指针,和一个用于收缩窗口的
 left 指针。在任意时刻,只有一个指针运动,而另一个保持静止。我们通过移动right指针不断扩张窗口。
 当窗口包含全部所需的字符后,如果能收缩,我们就收缩窗口直到得到最小窗口。
 答案是最小的可行窗口。

 注意:滑动窗口只需记录窗口两个指针即可,不一定需要使用linkedlist之类的做实体窗口!

 举个例子,S = "ABAACBAB",T = "ABC"。则问题答案是 "ACB"
 算法:
 1、初始,left指针和right指针都指向S的第一个元素.
 2、将 right 指针右移,扩张窗口,直到得到一个可行窗口,亦即包含T的全部字母的窗口。
 3、得到可行的窗口后,将left指针逐个右移,若得到的窗口依然可行,则更新最小窗口大小。
 4、若窗口不再可行,则跳转至 2。(这一步很重要,一直没想明白)
 遇到的问题:需要判断滑动窗口中是否包含t,这样要用到hashmap 的计数法,用一个变量cnt记录
 T中字符在S的滑动窗口中出现的次数。
 考虑用hashmap实现,
    1、遍历T,统计T串中字母的个数,而不是仅仅看某个字母是否在T串中出现。
    2、滑动窗口有窗口拓展过程:遍历S串,对于S中的字母,如果是T中的字符,映射hashmap
   中的value值如果大于0,cnt+1,(不能等于0,等于0,说明T中字符在S中出现过了),然后不
  管cnt是否加一,都要把value减一,以此记录滑动窗口中多余出现T中字符的数量,当用左窗口缩
 小的时候可以加回来。
    3、当cnt被加满等于T的长度时,说明滑动窗口中已经包含T所有字母的子串。此时开始左窗口缩小
 看最小能缩小到什么程度,先更新最小子串长度,开始缩小滑动窗口,加个if中对于S中的字母,如果
 是T中的字符,此时hashmap对应的value值可能为负数,对应说明滑动窗口中有多余的T字符,如果
 value+1>0,说明没有多余字符,即将要把必要的T字符排除在滑动窗口之外,这时cnt减一,然后不
 管cnt是否减一,都要把value加一,以此记录滑动窗口把多余出现T中的字符排除到滑动窗口外。缩小
 到cnt不等于T的长度,重复2、3.
 * @author LemonLin
 * @Description :StringMinimumWindowSubstring76LeetCode
 * @date 19.6.23-17:31
 */
public class StringMinimumWindowSubstring76LeetCode {
    public String minWindow(String s, String t) {
            //设置flag ,是为了排除s=a,t=aa的情况
            boolean flag= false;
            int left = 0;
            int lengthOfMin = s.length();
            int minLeft=0;
            int minRight=0;
            HashMap<Character,Integer> hashMap = new HashMap();
            int cnt=0;
            //遍历T串
            for (char c : t.toCharArray())
                hashMap.put( c, hashMap.containsKey(c) ? hashMap.get(c)+1 : 1);
            //遍历S串
            for (int right=0;right<s.length();right++){
                char temp = s.charAt(right);
                if (hashMap.containsKey(temp)){
                    cnt =hashMap.get(temp) > 0 ? cnt+1 : cnt;
                    hashMap.put(temp,hashMap.get(temp)-1);
                }
                while (cnt==t.length()){
                    flag=true;
                    if(right-left+1<=lengthOfMin){
                        lengthOfMin = right-left+1;
                        minLeft = left;
                        minRight = right;
                    }
                    char c = s.charAt(left);
                    if (hashMap.containsKey(c)){
                        if (hashMap.get(c)+1>0) cnt--;
                        hashMap.put(c,hashMap.get(c)+1);
                    }
                    left++;
                }
            }
            if (!flag)return "";
            return s.substring(minLeft,minRight+1);
        }
    public static void main(String[] args) {
            String s ="AA";
            String t = "AA";
            System.out.println(new StringMinimumWindowSubstring76LeetCode().minWindow(s, t));
    }
}

<think>我们使用滑动窗口(双指针)哈希集合(unordered_set)来高效解决。 思路: 1. 使用两个指针(leftright)表示窗口的左右边界,初始都指向0。 2. 使用一个哈希集合(unordered_set)记录窗口中的字符,保证无重复。 3. 右指针不断向右移动,将新字符加入窗口(集合): - 如果加入后没有重复,则更新最大长度。 - 如果有重复,则移动左指针,并从集合中移除左指针所指的字符,直到重复字符被移除(即窗口内再次无重复)。 4. 重复上述过程直到右指针到达字符串末尾。 注意:当遇到重复字符时,我们需要将左指针移动到重复字符的下一个位置,但注意重复字符可能不在左边界,所以需要循环移除直到重复字符被移除。 例子: "abcabcbb" 初始:left=0, right=0, 窗口内为空,maxLen=0 右指针移动:窗口"a" -> 无重复,maxLen=1 继续:窗口"ab" -> 无重复,maxLen=2 继续:窗口"abc" -> 无重复,maxLen=3 继续:加入'a',发现重复(因为集合中已有'a'),则移动左指针,移除窗口最左边的'a'(即left从0移动到1),此时窗口为"bca",继续。 但是,上面的例子中,当加入第二个'a'时,重复字符是'a',而集合中的'a'是第一个'a',所以我们需要将左指针移动到第一个'a'的下一个位置(即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: 字符'a',记录map['a']=0,maxLen=1 right=1: 字符'b',记录map['b']=1,maxLen=2 right=2: 字符'b',此时map['b']=1(在left=0的后面,所以是窗口内重复),所以left更新为map['b']+1=2,然后更新map['b']=2,当前长度=2-2+1=1,maxLen=2 right=3: 字符'a',此时map['a']=0(0<left=2,所以不在当前窗口内),所以不移动left,然后更新map['a']=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字符,可使用位图进一步减少空间占用
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值