Как найти наименьшую подстроку, которая содержит все символы из данной строки? - PullRequest
38 голосов
/ 17 марта 2010

Недавно я наткнулся на интересный вопрос о строках. Предположим, вам дано следующее:

Input string1: "this is a test string"
Input string2: "tist"
Output string: "t stri"

Итак, как указано выше, как мне подойти к поиску наименьшей подстроки строки1, содержащей все символы из строки 2?

Ответы [ 13 ]

39 голосов
/ 15 ноября 2010

Для получения более подробной информации, включая рабочий код, проверьте мой пост в блоге по адресу:

http://www.leetcode.com/2010/11/finding-minimum-window-in-s-which.html

Чтобы проиллюстрировать этот подход, я использую пример: string1 = "acbbaca" и string2 = "aba". Здесь мы также используем термин «окно», что означает непрерывный блок символов из строки1 (может быть заменен термином подстрока).

alt text

i) string1 = "acbbaca" и string2 = "aba".

alt text

ii) Первое минимальное окно найдено. Обратите внимание, что мы не можем начать заранее указатель как hasFound ['a'] == needToFind ['a'] == 2. Продвижение будет значит нарушить ограничение.

alt text

iii) Второе окно найдено. начать указатель по-прежнему указывает на первый элемент «а». hasFound ['a'] (3) is больше, чем NeedToFind ['a'] (2). Мы уменьшить значение hasFound ['a'] на единицу и указатель начала движения вправо.

alt text

iv) Мы пропускаем 'c', так как он не найден в строке2. Начальный указатель теперь указывает на «b». hasFound ['b'] (2) больше чем needToFind ['b'] (1). Мы уменьшаем hasFound ['b'] на единицу и начало продвижения указатель вправо.

alt text

v) Указатель начала теперь указывает на следующий «б». hasFound ['b'] (1) равно toToFind ['b'] (1). Мы стоим немедленно, и это наш новый найдено минимальное окно.

Идея в основном основана на помощи двух указателей (начальная и конечная позиции окна) и двух таблиц (needToFind и hasFound) при обходе строки1. NeedToFind хранит общее количество символов в string2, а hasFound хранит общее количество символов, встреченных до сих пор. Мы также используем переменную count для хранения всего количества символов в string2, которое встречалось до сих пор (не считая символов, где hasFound [x] превышает needToFind [x]). Когда count равен длине string2, мы знаем, что найдено верное окно.

Каждый раз, когда мы передвигаем указатель конца (указывающий на элемент x), мы увеличиваем hasFound [x] на единицу. Мы также увеличиваем число на единицу, если hasFound [x] меньше или равно needToFind [x]. Зачем? Когда ограничение выполнено (то есть число равно размеру строки 2), мы сразу же передвигаем указатель начала как можно дальше вправо, сохраняя ограничение.

Как мы проверяем, поддерживает ли оно ограничение? Предположим, что начало указывает на элемент x, мы проверяем, больше ли hasFound [x], чем needToFind [x]. Если это так, мы можем уменьшить hasFound [x] на единицу и продвинуть указатель начала без нарушения ограничения. С другой стороны, если это не так, мы немедленно останавливаемся, так как продвигающийся указатель начала нарушает ограничение окна.

Наконец, мы проверяем, меньше ли минимальная длина окна, чем текущий минимум. Обновите текущий минимум, если новый минимум найден.

По сути, алгоритм находит первое окно, которое удовлетворяет ограничению, а затем продолжает поддерживать ограничение повсюду.

33 голосов
/ 17 марта 2010

Вы можете выполнить сканирование гистограммы за O(N+M) время и O(1) пробел, где N - это количество символов в первой строке, а M - это количество символов во второй.

Работает так:

  • Создание гистограммы символов второй строки (операция клавиши hist2[ s2[i] ]++).
  • Составьте кумулятивную гистограмму символов первой строки, пока эта гистограмма не содержит все символы, которые содержит гистограмма второй строки (которую я назову «условием гистограммы»).
  • Затем двигайтесь вперед по первой строке, вычитая из гистограммы, пока она не будет соответствовать условию гистограммы. Отметьте этот бит первой строки (перед последним ходом) как предварительную подстроку.
  • Снова передвиньте переднюю часть подстроки вперед, пока не встретите условие гистограммы снова. Переместите конец вперед, пока он снова не потерпит неудачу. Если эта подстрока короче первой, отметьте ее в качестве предварительной подстроки.
  • Повторяйте, пока не пройдете всю первую строку.
  • Отмеченная подстрока - это ваш ответ.

Обратите внимание, что изменяя проверку, которую вы используете для условия гистограммы, вы можете выбрать либо такой же набор символов , что и в качестве второй строки, либо как минимум столько же символов каждого типа . (Это просто разница между a[i]>0 && b[i]>0 и a[i]>=b[i].)

Вы можете ускорить проверку гистограммы, если вы отслеживаете, какое условие не выполняется, когда вы пытаетесь его выполнить, и проверяете только то, что вы уменьшаете, когда вы пытаетесь его нарушить. (При начальном построении вы подсчитываете, сколько элементов вы удовлетворяете, и увеличиваете это число каждый раз, когда добавляете нового персонажа, который принимает условие от ложного до истинного.)

6 голосов
/ 17 марта 2010

Вот решение O (n). Основная идея проста: для каждого начального индекса найдите наименьший конечный индекс, чтобы подстрока содержала все необходимые буквы. Хитрость в том, что наименьший конечный индекс увеличивается по мере выполнения функции, поэтому при небольшой поддержке структуры данных мы рассматриваем каждый символ максимально дважды.

В Python:

from collections import defaultdict

def smallest(s1, s2):
    assert s2 != ''
    d = defaultdict(int)
    nneg = [0]  # number of negative entries in d
    def incr(c):
        d[c] += 1
        if d[c] == 0:
            nneg[0] -= 1
    def decr(c):
        if d[c] == 0:
            nneg[0] += 1
        d[c] -= 1
    for c in s2:
        decr(c)
    minlen = len(s1) + 1
    j = 0
    for i in xrange(len(s1)):
        while nneg[0] > 0:
            if j >= len(s1):
                return minlen
            incr(s1[j])
            j += 1
        minlen = min(minlen, j - i)
        decr(s1[i])
    return minlen
2 голосов
/ 23 мая 2016

Я получил тот же вопрос интервью. Я являюсь кандидатом C ++, но у меня была возможность довольно быстро кодировать в JAVA.

Java [Предоставлено Sumod Mathilakath]

import java.io.*;
import  java.util.*;

class UserMainCode
{


    public String GetSubString(String input1,String input2){
        // Write code here...
        return find(input1, input2);
    }
  private static boolean containsPatternChar(int[] sCount, int[] pCount) {
        for(int i=0;i<256;i++) {
            if(pCount[i]>sCount[i])
                return false;
        }
        return true;
    }
  public static String find(String s, String p) {
        if (p.length() > s.length())
            return null;
        int[] pCount = new int[256];
        int[] sCount = new int[256];
        // Time: O(p.lenght)
        for(int i=0;i<p.length();i++) {
            pCount[(int)(p.charAt(i))]++;
            sCount[(int)(s.charAt(i))]++;
        }
        int i = 0, j = p.length(), min = Integer.MAX_VALUE;
        String res = null;
        // Time: O(s.lenght)
        while (j < s.length()) {
            if (containsPatternChar(sCount, pCount)) {
                if ((j - i) < min) {
                    min = j - i;
                    res = s.substring(i, j);
                    // This is the smallest possible substring.
                    if(min==p.length())
                        break;
                    // Reduce the window size.
                    sCount[(int)(s.charAt(i))]--;
                    i++;
                }
            } else {
                sCount[(int)(s.charAt(j))]++;
                // Increase the window size.
                j++;
            }
        }
        System.out.println(res);
        return res;
    }
}

C ++ [Предоставлено: Sundeepblue]

#include <iostream>
#include <vector>
#include <string>
#include <climits>
using namespace std;
string find_minimum_window(string s, string t) {
    if(s.empty() || t.empty()) return;

    int ns = s.size(), nt = t.size();
    vector<int> total(256, 0);
    vector<int> sofar(256, 0);
    for(int i=0; i<nt; i++) 
        total[t[i]]++;

    int L = 0, R; 
    int minL = 0;                           //gist2
    int count = 0;
    int min_win_len = INT_MAX;

    for(R=0; R<ns; R++) {                   // gist0, a big for loop
        if(total[s[R]] == 0) continue;
        else sofar[s[R]]++;

        if(sofar[s[R]] <= total[s[R]])      // gist1, <= not <
            count++;

        if(count == nt) {                   // POS1
            while(true) {
                char c = s[L]; 
                if(total[c] == 0) { L++; }
                else if(sofar[c] > total[c]) {
                    sofar[c]--;
                    L++;
                }
                else break;
            }  
            if(R - L + 1 < min_win_len) {   // this judge should be inside POS1
                min_win_len = R - L + 1;
                minL = L;
            }
        }
    }
    string res;
    if(count == nt)                         // gist3, cannot forget this. 
        res = s.substr(minL, min_win_len);  // gist4, start from "minL" not "L"
    return res;
}
int main() {
    string s = "abdccdedca";
    cout << find_minimum_window(s, "acd");
}

Эрланг [Предоставлено: wardbekker]

-module(leetcode).

-export([min_window/0]).

%% Given a string S and a string T, find the minimum window in S which will contain all the characters in T in complexity O(n).

%% For example,
%% S = "ADOBECODEBANC"
%% T = "ABC"
%% Minimum window is "BANC".

%% Note:
%% If there is no such window in S that covers all characters in T, return the emtpy string "".
%% If there are multiple such windows, you are guaranteed that there will always be only one unique minimum window in S.



min_window() ->
    "eca" = min_window("cabeca", "cae"),
    "eca" = min_window("cfabeca", "cae"),
    "aec" = min_window("cabefgecdaecf", "cae"),
    "cwae" = min_window("cabwefgewcwaefcf", "cae"),
    "BANC" = min_window("ADOBECODEBANC", "ABC"),
    ok.

min_window(T, S) ->
    min_window(T, S, []).

min_window([], _T, MinWindow) ->
    MinWindow;
min_window([H | Rest], T, MinWindow) ->
    NewMinWindow = case lists:member(H, T) of
                       true ->
                           MinWindowFound = fullfill_window(Rest, lists:delete(H, T), [H]),
                           case length(MinWindow) == 0 orelse (length(MinWindow) > length(MinWindowFound)
                               andalso length(MinWindowFound) > 0) of
                               true ->
                                   MinWindowFound;
                               false ->
                                   MinWindow
                           end;
                       false ->
                           MinWindow
                   end,
    min_window(Rest, T, NewMinWindow).

fullfill_window(_, [], Acc) ->
    %% window completed
    Acc;
fullfill_window([], _T, _Acc) ->
    %% no window found
    "";
fullfill_window([H | Rest], T, Acc) ->
    %% completing window
    case lists:member(H, T) of
        true ->
            fullfill_window(Rest, lists:delete(H, T), Acc ++ [H]);
        false ->
            fullfill_window(Rest, T, Acc ++ [H])
    end.

REF:

1 голос
/ 30 июля 2011

Пожалуйста, посмотрите на это:

//-----------------------------------------------------------------------

bool IsInSet(char ch, char* cSet)
{
    char* cSetptr = cSet;
    int index = 0;
    while (*(cSet+ index) != '\0')
    {
        if(ch == *(cSet+ index))
        {
            return true;            
        }
        ++index;
    }
    return false;
}

void removeChar(char ch, char* cSet)
{
    bool bShift = false;
    int index = 0;
    while (*(cSet + index) != '\0')
    {
        if( (ch == *(cSet + index)) || bShift)
        {
            *(cSet + index) = *(cSet + index + 1);
            bShift = true;
        }
        ++index;
    }
}
typedef struct subStr
{
    short iStart;
    short iEnd;
    short szStr;
}ss;

char* subStringSmallest(char* testStr, char* cSet)
{
    char* subString = NULL;
    int iSzSet = strlen(cSet) + 1;
    int iSzString = strlen(testStr)+ 1;
    char* cSetBackUp = new char[iSzSet];
    memcpy((void*)cSetBackUp, (void*)cSet, iSzSet);

    int iStartIndx = -1;    
    int iEndIndx = -1;
    int iIndexStartNext = -1;

    std::vector<ss> subStrVec;
    int index = 0;

    while( *(testStr+index) != '\0' )
    {
        if (IsInSet(*(testStr+index), cSetBackUp))
        {
            removeChar(*(testStr+index), cSetBackUp);

            if(iStartIndx < 0)
            {
                iStartIndx = index;
            }
            else if( iIndexStartNext < 0)
                iIndexStartNext = index;
            else
                ;

            if (strlen(cSetBackUp) == 0 )
            {
                iEndIndx = index;
                if( iIndexStartNext == -1)
                    break;
                else
                {
                    index = iIndexStartNext;
                    ss stemp = {iStartIndx, iEndIndx, (iEndIndx-iStartIndx + 1)};
                    subStrVec.push_back(stemp);
                    iStartIndx = iEndIndx = iIndexStartNext = -1;
                    memcpy((void*)cSetBackUp, (void*)cSet, iSzSet);
                    continue;
                }
            }
        }
        else
        {
            if (IsInSet(*(testStr+index), cSet))
            {
                if(iIndexStartNext < 0)
                    iIndexStartNext = index;
            }
        }

        ++index;
    }


    int indexSmallest = 0;
    for(int indexVec = 0; indexVec < subStrVec.size(); ++indexVec)
    {
        if(subStrVec[indexSmallest].szStr > subStrVec[indexVec].szStr)
            indexSmallest = indexVec;       
    }

    subString = new char[(subStrVec[indexSmallest].szStr) + 1];
    memcpy((void*)subString, (void*)(testStr+ subStrVec[indexSmallest].iStart), subStrVec[indexSmallest].szStr);
    memset((void*)(subString + subStrVec[indexSmallest].szStr), 0, 1);

    delete[] cSetBackUp;
    return subString;
}
//--------------------------------------------------------------------
0 голосов
/ 25 октября 2018

Я реализовал это, используя Python3 с эффективностью O (N):

def get(s, alphabet="abc"):
    seen = {}
    for c in alphabet:
        seen[c] = 0
    seen[s[0]] = 1
    start = 0
    end = 0
    shortest_s = 0
    shortest_e = 99999
    while end + 1 < len(s):
        while seen[s[start]] > 1:
            seen[s[start]] -= 1
            start += 1
        # Constant time check:
        if sum(seen.values()) == len(alphabet) and all(v == 1 for v in seen.values()) and \
                shortest_e - shortest_s > end - start:
            shortest_s = start
            shortest_e = end
        end += 1
        seen[s[end]] += 1
    return s[shortest_s: shortest_e + 1]


print(get("abbcac")) # Expected to return "bca"
0 голосов
/ 18 февраля 2017

C # Реализация:

public static Tuple<int, int> FindMinSubstringWindow(string input, string pattern)
{
    Tuple<int, int> windowCoords = new Tuple<int, int>(0, input.Length - 1);
    int[] patternHist = new int[256];
    for (int i = 0; i < pattern.Length; i++)
    {
        patternHist[pattern[i]]++;
    }
    int[] inputHist = new int[256];
    int minWindowLength = int.MaxValue;
    int count = 0;
    for (int begin = 0, end = 0; end < input.Length; end++)
    {
        // Skip what's not in pattern.
        if (patternHist[input[end]] == 0)
        {
            continue;
        }
        inputHist[input[end]]++;
        // Count letters that are in pattern.
        if (inputHist[input[end]] <= patternHist[input[end]])
        {
            count++;
        }
        // Window found.
        if (count == pattern.Length)
        {
            // Remove extra instances of letters from pattern
            // or just letters that aren't part of the pattern
            // from the beginning.
            while (patternHist[input[begin]] == 0 ||
                   inputHist[input[begin]] > patternHist[input[begin]])
            {
                if (inputHist[input[begin]] > patternHist[input[begin]])
                {
                    inputHist[input[begin]]--;
                }
                begin++;
            }
            // Current window found.
            int windowLength = end - begin + 1;
            if (windowLength < minWindowLength)
            {
                windowCoords = new Tuple<int, int>(begin, end);
                minWindowLength = windowLength;
            }
        }
    }
    if (count == pattern.Length)
    {
        return windowCoords;
    }
    return null;
}
0 голосов
/ 26 сентября 2016
def minimum_window(s, t, min_length = 100000):
    d = {}
    for x in t:
        if x in d:
            d[x]+= 1
        else:
            d[x] = 1

    tot = sum([y for x,y in d.iteritems()])
    l = []
    ind = 0 
    for i,x in enumerate(s):
        if ind == 1:
            l = l + [x]
        if x in d:
            tot-=1
            if not l:
                ind = 1
                l = [x]

        if tot == 0:
            if len(l)<min_length:
                min_length = len(l)
                min_length = minimum_window(s[i+1:], t, min_length)

return min_length

l_s = "ADOBECODEBANC"
t_s = "ABC"

min_length = minimum_window(l_s, t_s)

if min_length == 100000:
      print "Not found"
else:
      print min_length
0 голосов
/ 16 августа 2016

Это подход, использующий простые числа, чтобы избежать одного цикла и заменить его умножением. Несколько других незначительных оптимизаций могут быть сделаны.

  1. Назначьте уникальное простое число любому из символов, которые вы хотите найти, и 1 неинтересным символам.

  2. Найдите произведение совпадающей строки, умножив простое число на количество вхождений, которое оно должно иметь. Теперь этот продукт можно найти, только если используются те же простые множители.

  3. Поиск строки с начала, умножение соответствующего простого числа при переходе в запущенный продукт.

  4. Если число больше правильной суммы, удалите первый символ и разделите его простое число из вашего бегущего продукта.

  5. Если число меньше правильной суммы, включите следующий символ и умножьте его на свой бегущий продукт.

  6. Если число совпадает с правильной суммой, в которой вы нашли совпадение, сдвиньте начало и конец следующего символа и продолжите поиск других совпадений.

  7. Решите, какой из матчей самый короткий.

* 1035 Gist *

charcount = { 'a': 3, 'b' : 1 };
str = "kjhdfsbabasdadaaaaasdkaaajbajerhhayeom"

def find (c, s):
  Ns = len (s)

  C = list (c.keys ())
  D = list (c.values ())

  # prime numbers assigned to the first 25 chars
  prmsi = [ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89 , 97]

  # primes used in the key, all other set to 1
  prms = []
  Cord = [ord(c) - ord('a') for c in C]

  for e,p in enumerate(prmsi):
    if e in Cord:
      prms.append (p)
    else:
      prms.append (1)

  # Product of match
  T = 1
  for c,d in zip(C,D):
    p = prms[ord (c) - ord('a')]
    T *= p**d

  print ("T=", T)

  t = 1 # product of current string
  f = 0
  i = 0

  matches = []
  mi = 0
  mn = Ns
  mm = 0

  while i < Ns:
    k = prms[ord(s[i]) - ord ('a')]
    t *= k

    print ("testing:", s[f:i+1])

    if (t > T):
      # included too many chars: move start
      t /= prms[ord(s[f]) - ord('a')] # remove first char, usually division by 1
      f += 1 # increment start position
      t /= k # will be retested, could be replaced with bool

    elif t == T:
      # found match
      print ("FOUND match:", s[f:i+1])
      matches.append (s[f:i+1])

      if (i - f) < mn:
        mm = mi
        mn = i - f

      mi += 1

      t /= prms[ord(s[f]) - ord('a')] # remove first matching char

      # look for next match
      i += 1
      f += 1

    else:
      # no match yet, keep searching
      i += 1

  return (mm, matches)


print (find (charcount, str))

(примечание: этот ответ был первоначально размещен на дублирующийся вопрос, оригинальный ответ теперь удален.)

0 голосов
/ 02 августа 2016
//[ShortestSubstring.java][1]

public class ShortestSubstring {

    public static void main(String[] args) {
        String input1 = "My name is Fran";
        String input2 = "rim";
        System.out.println(getShortestSubstring(input1, input2));
    }

    private static String getShortestSubstring(String mainString, String toBeSearched) {

        int mainStringLength = mainString.length();
        int toBeSearchedLength = toBeSearched.length();

        if (toBeSearchedLength > mainStringLength) {
            throw new IllegalArgumentException("search string cannot be larger than main string");
        }

        for (int j = 0; j < mainStringLength; j++) {
            for (int i = 0; i <= mainStringLength - toBeSearchedLength; i++) {
                String substring = mainString.substring(i, i + toBeSearchedLength);
                if (checkIfMatchFound(substring, toBeSearched)) {
                    return substring;
                }
            }
            toBeSearchedLength++;
        }

        return null;
    }

    private static boolean checkIfMatchFound(String substring, String toBeSearched) {
        char[] charArraySubstring = substring.toCharArray();
        char[] charArrayToBeSearched = toBeSearched.toCharArray();
        int count = 0;

        for (int i = 0; i < charArraySubstring.length; i++) {
            for (int j = 0; j < charArrayToBeSearched.length; j++) {
                if (String.valueOf(charArraySubstring[i]).equalsIgnoreCase(String.valueOf(charArrayToBeSearched[j]))) {
                    count++;
                }
            }
        }
        return count == charArrayToBeSearched.length;
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...