解题法-滑动窗口

 3. 无重复字符的最长子串

使用set来存储窗口

    int lengthOfLongestSubstring(string s) {
        unordered_set<char> set;
        int ans = 0, right = 0;
        
        for (int i=0; i < s.length(); i++)
        {
            // 右指针右移,直到遇到重复字符
            while (right < s.length() && 0==set.count(s[right]))
            {
                set.insert(s[right]);
                right++;
            }
            // 计算下当前的长度
            ans = max(ans, right - i);
            // 移除最左边的重复字符
            if (set.size() > 0)
            {
                set.erase(s[i]);
            }
          
        }

        return ans;

 30. 串联所有单词的子串

继续滑动窗口,使用了两个hash,tFreq是记录目标窗口中各个单词出现的频数,winFreq记录当前窗口中各个单词出现的频数,同时使用distance记录两个窗口的差异。

由于是单词比较,s 的起点不同,所划分的单词也不同,所以大循环是用单词长度划分 s

在窗口的滑动中,要处理下面3中情况

  1. 新的单词并不在words里
  2. 遇到了重复次数过多的单词
  3. 完全匹配
class Solution {
public:
    vector<int> findSubstring(string s, vector<string>& words) {
        vector<int> ans = {};
        unordered_map<string, int> winFreq, tFreq; // 当前窗口的单词出现频数 和 目标的单词出现频数
        int left=0, right = 0, distance = words.size(); // distance 表示 当前窗口与目标窗口的差异, 0 表示完全匹配
        int lenWords = 0;
        int lenS = s.length();

        if (words.size() == 0 || s.length() == 0)
        {
            return ans;
        }
        int wordSize = words[0].size();
        lenWords = wordSize * words.size();
        if (lenS < lenWords)
        {
            return ans;
        }
        
        for (string s : words)
        {
            tFreq[s]++;
        }
        
        // 将 s 以单个单词长度分类
        for (int start = 0; start < wordSize; start++)
        {
            // 每一轮的初始条件(曾因为 distance 和 winFreq 没有重置导致多次提交失败)
            left = start, right = start, distance = words.size(), winFreq.clear();

            while(right + wordSize <= lenS)
            {
                string subStr = s.substr(right, wordSize);
                // 1. 存在, 是目标单词
                if (tFreq.find(subStr) != tFreq.end())
                {
                    winFreq[subStr]++;
                    distance--;
                    //  有重复,需要去重, 缩小窗口
                    while (winFreq[subStr] > tFreq[subStr]) 
                    {
                        string firstStr = s.substr(left, wordSize);
                        winFreq[firstStr]--;
                        distance++;
                        left += wordSize;
                    }  
                }
                 // 2. 不存在
                else
                {
                    left = right + wordSize;
                    distance = words.size();
                    winFreq.clear();
                }

                if (0 == distance)
                {
                    ans.push_back(left);
                }  

                right += wordSize;
            }
            
        }
        return ans;
    }
};

76. 最小覆盖子串

继续滑动,保持左边窗口不变,右窗口滑动,直到匹配所有字符,然后考虑缩减左边窗口;

数据结构上,使用hash记录当前窗口是否已经包含了t中的所有字符;同时利用负数表示不需要的字符,正数表示需要的字符,避免了使用find

class Solution {
public:

    string minWindow(string s, string t) {
        // set hash queue stack list tree
        const int lenS = s.length(), lenT = t.length();
        int ansLen = INT_MAX, start = 0;
        int left = 0, right = 0, k = lenT;// k 记录当前所需字符数

        vector<int> need(128, 0); // 记录当前窗口下t中各个字符缺少的个数
        // 初始化,缺t中的所有字符
        for (int i=0; i<lenT; i++)
        {
            need[t[i]]++; 
        }

        for (right = 0; right < lenS; right++)
        {
            if (need[s[right]] > 0)
            {
                k--;
            }
            need[s[right]]--; // 需要数减 1
            if (k == 0) // found all 
            {
                // 尝试缩减窗口
                while (left < right && need[s[left]] < 0) // 窗口左边存在不需要的字符
                {
                    need[s[left]]++;
                    left++;
                }
                // 遇到了第一个需要的字符,此时窗口符合要求
                if (right - left + 1 < ansLen)
                {
                    ansLen = right-left+1;
                    start = left;
                }
                // 窗口左移一位,释放need[s[left]]
                need[s[left]]++;
                left++;
                k++;
            }
        }
        
        if (ansLen == INT_MAX)
        {
            return "";
        }
        else
        {
            return s.substr(start, ansLen);
        }

    }
};

159. 至多包含两个不同字符的最长子串

使用hash记录当前窗口下各个字符的个数,使用计数器count记录窗口中不相同的字符个数;当count > 2时,就需要记录当前「可行」窗口大小,并缩小左边窗口,删除1个旧字符。

考虑的特殊情况有:

  1. s 中只有2个不相同的字符
  2. s 的长度小于3
    int lengthOfLongestSubstringTwoDistinct(string s) {
        int lenS = s.length();
        int left =0, right = 0;
        int ans = 0;
        int count = 0, k = 2;
        unordered_map<char, int> hash;

        if (lenS <= k)
            return lenS;

        for (right =0; right < lenS; right++)
        {
            hash[s[right]]++;
            if (hash[s[right]] == 1) // 遇到新字符
            {
                count++;
            }
            // 遇到了第三个字符,缩减左边窗口,删除1个字符
            if (count > k)
            {
                // 计算当前子串长度
                ans = max(ans, right-left);
                while(count > k && left < right)
                {
                    hash[s[left]]--;
                    if (hash[s[left]] == 0)
                    {
                        count--;
                    }
                    left++;
                }
            }
        }
        ans = max(ans, right-left); // 考虑只有2个非重复字符的情况
        return ans;
    }

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值