查找最小窗口子字符串

时间:2015-09-08 09:11:58

标签: python string algorithm

给定字符串S和字符串T,找到S中的最小窗口,其中包含复杂度为O(n)的T中的所有字符。

例如,

S = "ADOBECODEBANC"
T = "ABC"

最小窗口是" BANC"。

更新

我从http://articles.leetcode.com/2010/11/finding-minimum-window-in-s-which.html读取了一个实现,并且实现似乎错了,这不会减少计数,并且在找到匹配的窗口时也不会开始移动?感谢。

// Returns false if no valid window is found. Else returns 
// true and updates minWindowBegin and minWindowEnd with the 
// starting and ending position of the minimum window.
bool minWindow(const char* S, const char *T, 
               int &minWindowBegin, int &minWindowEnd) {
  int sLen = strlen(S);
  int tLen = strlen(T);
  int needToFind[256] = {0};

  for (int i = 0; i < tLen; i++)
    needToFind[T[i]]++;

  int hasFound[256] = {0};
  int minWindowLen = INT_MAX;
  int count = 0;
  for (int begin = 0, end = 0; end < sLen; end++) {
    // skip characters not in T
    if (needToFind[S[end]] == 0) continue;
    hasFound[S[end]]++;
    if (hasFound[S[end]] <= needToFind[S[end]])
      count++;

    // if window constraint is satisfied
    if (count == tLen) {
      // advance begin index as far right as possible,
      // stop when advancing breaks window constraint.
      while (needToFind[S[begin]] == 0 ||
            hasFound[S[begin]] > needToFind[S[begin]]) {
        if (hasFound[S[begin]] > needToFind[S[begin]])
          hasFound[S[begin]]--;
        begin++;
      }

      // update minWindow if a minimum length is met
      int windowLen = end - begin + 1;
      if (windowLen < minWindowLen) {
        minWindowBegin = begin;
        minWindowEnd = end;
        minWindowLen = windowLen;
      } // end if
    } // end if
  } // end for

  return (count == tLen) ? true : false;
}

5 个答案:

答案 0 :(得分:5)

假设字符串S和T仅包含A-Z个字符(26个字符)

  • 首先,创建一个数组count,用于存储T中每个字符的频率。

  • 处理S中的每个字符,维护一个窗口l, r,这将是包含T中所有字符的当前最小窗口。

  • 我们维护一个数组cur来存储窗口中当前字符的频率。如果窗口左端角色的频率高于所需频率,我们会增加l

示例代码:

    int[]count = new int[26];
    for(int i = 0; i < T.length; i++)
        count[T[i] - 'A']++;

    int need = 0;//Number of unique characters in T
    for(int i = 0; i < 26; i++)
        if(count[i] > 0)
           need++;
    int l = 0, r = 0;
    int count = 0;
    int result ;
    int[]cur = new int[26];
    for(int i = 0; i < S.length; i++){
         cur[S[i] - 'A']++;
         r = i;
         if(cur[S[i] - 'A'] == count[S[i] - `A`]){
             count++;                 
         }
         //Update the start of the window,
         while(cur[S[l] - 'A'] > count[S[l] - 'A']){
               cur[S[l] - 'A']--;
               l++;
         } 
         if(count == need)
             result = min(result, r - l + 1);
    }

S中的每个字符最多将被处理两次,这给了我们O(n)的复杂性。

答案 1 :(得分:2)

def minWindow(self, s, t):
    """
    :type s: str
    :type t: str
    :rtype: str
    """
    count = len(t)
    require = [0] * 128
    chSet = [False] * 128
    for i in range(count):
        require[ord(t[i])] += 1
        chSet[ord(t[i])] = True
    i = -1
    j = 0
    minLen = 999999999
    minIdx = 0
    while i < len(s) and j < len(s):
        if count > 0:
            i += 1
            if i == len(s):
                index = 0
            else:
                index = ord(s[i])
            require[index] -= 1
            if chSet[index] and require[index] >=0:
                count -= 1
        else:
            if minLen > i - j + 1:
                minLen = i - j + 1
                minIdx = j
            require[ord(s[j])] += 1
            if chSet[ord(s[j])] and require[ord(s[j])] > 0:
                count += 1
            j += 1
    if minLen == 999999999:
        return ""
    return s[minIdx:minIdx+minLen]  

我使用的方法是映射字符以及子字符串中的字符数与需要的字符数。如果所有值都是非负数,则可以从子字符串的开头删除字符,直到达到负数,如果存在负数,则将其添加到子字符串的末尾,直到它再次为0。你继续这个直到你到达S的结尾,然后删除字符,直到你有一个字符为负数。

通过示例,S =“ADOBECODEBANC”,T =“ABC”。首先,地图的值为A = -1,B = -1,C = -1,并且计数为3个底数。添加第一个字母会将A增加到0,这将删除负数,将计数减去2.您也可以计算其他数字,因为它们永远不会变为负数,从而导致A = 0,B = 0,C = 0,D当您添加C时,= 1,O = 1,E = 1.由于负计数为0,您开始从开始删除字符,即A,将其删除为-1,然后切换回最后添加。

然后添加到最后,直到再次到达A,这导致A = 0,B = 1,C = 0,D = 2,E = 2,O = 2并且计数为0.从开始直到你再次达到负值,这将删除D,O,B,E,C,因为B的删除只会将其降为0,而不是负数。此时,子串是“ODEBA”,C = -1。添加到最后,直到你达到C并且你有“ODEBANC”,并从开始删除,直到你再次得到负面,留下“ANC”。你已到达字符串的末尾并且为负数,因此所有字符都没有更短的字符串。

您可以通过获取映射子字符串的开始和结束索引来检索最短子字符串,只要它们从删除切换到添加并存储它们(如果它们比前一个最短)。如果你从不切换到删除,那么结果就是空字符串。

如果S =“BANC”且T =“ABC”,则结果将添加,直到达到“BANC”,切换到删除,击中负数(因此将这些长度复制到0和3),并尝试超出结束算法的结尾,子串从0开始,到3结束。

当每个角色添加一次并删除一次或更少时,最多需要2n步才能完成算法,即O(n)解决方案。

Idea from mike3

答案 2 :(得分:1)

您可以尝试以下方法:

  • 创建T的哈希值(因为t中的字符顺序无关紧要,我们将使用其哈希值)
  • 现在选择两个指针(迭代S),两者都以0开头索引。让他们的名字为i,j
  • 在每一步增加j并在您前进时计算S的哈希值。当这个哈希涵盖T的哈希值时(当然你需要比较每一步的两个哈希值),开始递增i(并递减哈希值S中的哈希值)直到哈希仍然被覆盖。
  • S&lt;的散列时T的哈希值,再次增加j
  • 在任何时候,涵盖i..j哈希值的T的最小窗口大小就是您的答案。

PS:照顾角落的情况,比如字符串的结尾和所有。如果您需要代码我会帮忙,但如果您先自己尝试然后再提出疑问,我会建议。

答案 3 :(得分:1)

更多&#39; pythonic&#39;关于http://articles.leetcode.com/2010/11/finding-minimum-window-in-s-which.html

中解释的算法的方法

简而言之:使用头部和尾部的指针&#39;,前进头直到找到匹配,然后前进尾部(减小窗口的大小),而子串仍然匹配

    UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Pop" bundle:nil];
    UIViewController *vc = (UIViewController *)[storyboard instantiateViewControllerWithIdentifier:@"FreshPopViewController"];

    firstPopView = vc.view;
    vc.view = nil;

    //firstPopView.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height);
    //[firstPopView setFrame:self.view.frame];
    [firstPopView setFrame:self.view.bounds];

    UIButton *requestButton = (UIButton*)[self.view viewWithTag:101];

    [requestButton addTarget:self action:@selector(newRequestButtonClicked) forControlEvents:UIControlEventTouchUpInside];

    [self.view addSubview:firstPopView];

答案 4 :(得分:0)

我的C ++解决方案的运行时间为O(n)(leetcode接受的解决方案,其运行速度比提交的C ++解决方案的99%快):

#include<string>
#include<vector>

using namespace std;

class CharCounter
{
private:
    const int fullCount;
    int currentCount;
    vector<pair<short, short>> charMap;

public:
    CharCounter(const string &str) :fullCount(str.size()), currentCount(0) {
        charMap = vector<pair<short, short>>(128, { 0,0 });
        for (const auto ch : str) {
            charMap[ch].second++;
        }
    };

    void reset() {
        for (auto &entry : charMap)
            entry.first = 0;

        currentCount = 0;
    }
    bool complete() const {
        return  (currentCount == fullCount);
    }

    void add(char ch) {
        if (charMap[ch].second > 0) {
            if (charMap[ch].first < charMap[ch].second)
                currentCount++;

            charMap[ch].first++;
        }
    }

    void subtract(char ch) {
        if (charMap[ch].second > 0) {
            if (charMap[ch].first <= charMap[ch].second)
                currentCount--;
            charMap[ch].first--;
        }
    }
};

class Solution
{
public:
    string minWindow(string s, string t) {
        if ((s.size() < 1) || (t.size() < 1))
            return "";

        CharCounter counter(t);
        pair<size_t, size_t> shortest = { 0, numeric_limits<size_t>::max() };
        size_t beg = 0, end = 0;
        while (end < s.size()) {
            while ((end < s.size()) && (!counter.complete())) {
                counter.add(s[end]);
                if (counter.complete())
                    break;
                end++;
            }
            while (beg < end) {
                counter.subtract(s[beg]);
                if (!counter.complete()) {
                    counter.add(s[beg]);
                    break;
                }
                beg++;
            }
            if (counter.complete()) {
                if ((end - beg) < shortest.second - shortest.first) {
                    shortest = { beg, end };
                    if (shortest.second - shortest.first + 1 == t.size())
                        break;
                }
                if (end >= s.size() - 1)
                    break;
                counter.subtract(s[beg++]);
                end++;
            }
        }
        return s.substr(shortest.first, shortest.second - shortest.first + 1);
    }
};

这个想法很简单:使用两个“指针” sbeg逐个字符地迭代源字符串(end)char。添加在end遇到的每个字符。一旦添加了t中包含的所有主席,请更新最短间隔。递增左指针beg并从计数器中减去左字符。 用法示例:

int main()
{
    Solution fnder;
    string str = "figehaeci";
    string chars = "aei";
    string shortest = fnder.minWindow(str, chars);  // returns "aeci"
}

CharCounter的唯一目的是计算t中包含的遇到的字符。