LintCode 32: Minimum Window Substring (同向双指针好题)

本文深入探讨了寻找包含目标字符串所有字符的最小子串问题,通过三种不同的同向双指针策略,详细讲解了解决方案的设计思路与实现代码,为读者提供了清晰的算法理解和实践指导。

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

  1. Minimum Window Substring
    中文English
    Given two strings source and target. Return the minimum substring of source which contains each char of target.

Example
Example 1:

Input: source = “abc”, target = “ac”
Output: “abc”
Example 2:

Input: source = “adobecodebanc”, target = “abc”
Output: “banc”
Explanation: “banc” is the minimum substring of source string which contains each char of target “abc”.
Example 3:

Input: source = “abc”, target = “aa”
Output: “”
Explanation: No substring contains two ‘a’.
Challenge
O(n) time

Notice
If there is no answer, return “”.
You are guaranteed that the answer is unique.
target may contain duplicate char, while the answer need to contain at least the same number of that char.

解法1:
这题思路就是用同向双指针,但是我觉得相当不容易。

  1. p1从0开始,p2先往右移动,一直到p1…p2之间的窗口覆盖所有的target字母。然后p1再往右移动一个,若
    窗口已经不能覆盖所有字母了,则p2又往右移动一个,直到边界。
  2. count表示当前窗口(p1+1…p2)里面还缺count个target的元素。所以一旦count>0,p2必须往右移动。
  3. if (mp[source[p1]] == 0) count++;即source[p1]是一个target里面的元素,此时window罩不住target里面的元素了。注意p1在p2后面,p2已经处理过了,所以mp[source[p1]]=0的话一定是一个target里面的元素。
    代码如下:
class Solution {
public:
    /**
     * @param source : A string
     * @param target: A string
     * @return: A string denote the minimum window, return "" if there is no such a string
     */
    string minWindow(string &source , string &target) {
        int ns = source.size();
        int nt = target.size();
        
        map<char, int> mp;
        for (int i = 0; i < nt; ++i) {
            mp[target[i]]++;
        }
        int count = mp.size();
        
        int p1 = 0, p2 = 0;
        int minLen = INT_MAX;
        string minResult;
        
        while(p1 < ns) {
            while(p2 < ns && count > 0) {
                mp[source[p2]]--;
                if (mp[source[p2]] == 0) count--;
                p2++;
                if (count == 0) break;
            }
            
            if (count == 0) {
                int curWinSize = p2 - p1;// + 1;
                if (curWinSize < minLen) {
                    minLen = curWinSize;
                    minResult = source.substr(p1, curWinSize);
                }
            }
    
            if (mp[source[p1]] == 0) count++;
            mp[source[p1]]++;
            p1++;
        }
        
        return minResult;
    }
};

解法2:参考了Grandyang的解法。slidingwindow。很妙。

class Solution {
public:
    /**
     * @param source: A string
     * @param target: A string
     * @return: A string denote the minimum window, return "" if there is no such a string
     */
    string minWindow(string &source, string &target) {
        int source_len = source.size();
        int target_len = target.size();
        
        vector<int> freq_target(256, 0);
        int cnt = 0;
        for (int i = 0; i < target_len; i++) {
            freq_target[target[i]]++;
        }
        bool found = false;
        int left = 0;
        int saved_left = 0, saved_right = source_len - 1;
        for (int i = 0; i < source_len; i++) {
            if (--freq_target[source[i]] >= 0)  cnt++;
            while (cnt == target_len) {
                if (saved_right - saved_left >= i - left) {
                    saved_right = i;
                    saved_left = left;
                    found = true;
                  }      
                if (++freq_target[source[left]] > 0) cnt--;
                ++left;
            }
        }
        if (found) return source.substr(saved_left, saved_right - saved_left + 1); 
        return "";
    }
};

解法3:同向双指针模板。思路同lintcode 1169.
https://blog.youkuaiyun.com/roufoo/article/details/127944794?spm=1001.2014.3001.5501

class Solution {
public:
    /**
     * @param source: A string
     * @param target: A string
     * @return: A string denote the minimum window, return "" if there is no such a string
     */
    string minWindow(string &source, string &target) {
        int lenS = source.size();
        int lenT = target.size();
        if (lenS < lenT) return "";
        string res;
        vector<int> freq(128, 0);
        for (int i = 0; i < lenT; i++) freq[target[i]]++;
        int count = lenT, j = 0, minWin = lenS;
        for (int i = 0; i < lenS; i++) {
            j = max(i, j); //i窗口左边界,j窗口右边界。
            while (count > 0 && j < lenS) {
                if (freq[source[j]] > 0) count--;
                freq[source[j]]--;
                j++;
            }
            if (count == 0) {
                if (minWin >= j - i) {
                    minWin = j - i;
                    res = source.substr(i, j - i);
                }
                if (freq[source[i]] == 0) count++; //此时source[i]是一个在target中的字符,即将移出窗口i..j,所以count++。注意target中可能有多个source[i],不过没关系,count==0说明target中的字符都在source[i..j]中找到且个数刚刚好,移出一个,那么count++。
                freq[source[i]]++;
            } else { //此时count > 0,说明j已经到末尾,i..j之间这么大的距离都不满足要求,那i也不用往后面移动了,退出循环。
                break;
            }
        }
        return res;
    }
};

二刷:

class Solution {
public:
    /**
     * @param source: A string
     * @param target: A string
     * @return: A string denote the minimum window, return "" if there is no such a string
     */
    string minWindow(string &source, string &target) {
        int lenS = source.size();
        int lenT = target.size();
        if (lenS < lenT) return "";
        int count = lenT;
        vector<int> freq(128, 0);
        for (int i = 0; i < lenT; i++) {
            freq[target[i]]++;
        }
        int left = 0, right = 0, minLen = lenS + 1;
        string minLenStr = "";
        for (left = 0; left < lenS; left++) {
            right = max(left, right);
            while (count > 0 && right < lenS) {
                if (freq[source[right]] > 0) {
                    count--;
                }

                freq[source[right]]--;
                right++;
            }
            //if (right == lenS && count > 0) return "";
            if (count == 0) {
                if (minLen > right - left) { //not right-left+1 as right++ in while()
                    minLen = right - left;
                    minLenStr = source.substr(left, right - left);
                }
            }
            
            if (freq[source[left]] == 0) count++; //这一步很重要
            freq[source[left]]++;
        }
        return minLenStr;
    }
};

注意上面的if (freq[source[left]] == 0) count++;很重要。
如果source=“tttttttts”, target = “tts”, 那么,第一个for循环扫描target,得到freq[t]=2。然后第二个for循环的while循环会一直往右扫描,直到最后的tts,这样freq[t]=-6 (left=0, right =8), count=0 。然后for循环会移动left,这里right不会动因为count==0,每次for迭代freq[t]++。当left=6时,freq[t]=6,此时可以认为这个t是source[left, right]里面的一个target的字母,所以count++。

三刷:也是同向双指针。
注意同向双指针有两种。一种是上面的二刷 – 先尽量把右指针往右移动,直到出现一个可行解,然后再把左指针挨个挨个往右移,每次如果还是可行解的话则记录窗口大小和相应值。用我自己的总结来说就是大力移右,挨个移左。
另一种就是这里的方法–先挨个挨个把右指针往右移,如果出现了一个可行解则尽量把左指针往右移,在可行解的范围内能移动多少移动多少,每次记录窗口大小和相应值。用我自己的总结来说就是挨个移右,大力移左。
注意:validCount的加减都必须根据freqS[c] == freqT[c],而不是>或>=!

class Solution {
public:
    string minWindow(string s, string t) {
        int sLen = s.size(), tLen = t.size(), needCount = 0;
        vector<int> freqS(128, 0), freqT(128, 0);
        for (int i = 0; i < tLen; i++) {
            if (freqT[t[i]] == 0) needCount++;
            freqT[t[i]]++;
        }
        int p1 = 0, p2 = 0;
        int validCount = 0;
        int start = -1, len = sLen; 
        while (p2 < sLen) {
            char c = s[p2];
            freqS[c]++;
            if (freqT[c] > 0) {
                if (freqS[c] == freqT[c]) validCount++;
            }
            p2++;
            while (validCount == needCount) {
                c = s[p1];
                if (len >= p2 - p1) {
                    len = p2 - p1;
                    start = p1;
                }
                if (freqS[c] == freqT[c]) {
                    validCount--;
                }                
                freqS[c]--;
                p1++;
            }
        }
        return start >= 0 ? s.substr(start, len) : "";
    }
};
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值