web-dev-qa-db-ja.com

特定の文字列のすべての文字を含む最小の部分文字列を見つける方法は?

私は最近、文字列に関する興味深い質問に出会いました。次のものが与えられたとします:

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

したがって、上記のように、文字列2のすべての文字を含むstring1の最小の部分文字列を見つけるためにどのようにアプローチできますか?

39
Rajendra Uppal

O(N+M) timeおよびO(1) spaceでヒストグラムスイープを実行できます。ここで、Nは最初の文字列の文字数、Mは数字です秒の文字の。

それはこのように動作します:

  • 2番目の文字列の文字のヒストグラムを作成します(キー操作はhist2[ s2[i] ]++)。
  • 2番目の文字列のヒストグラムに含まれるすべての文字がヒストグラムに含まれるまで、最初の文字列の文字の累積ヒストグラムを作成します(これを「ヒストグラム条件」と呼びます)。
  • 次に、最初の文字列を前に移動し、ヒストグラムの条件を満たさなくなるまで、ヒストグラムから減算します。最初の文字列のそのビット(最終移動の前)を仮の部分文字列としてマークします。
  • ヒストグラムの条件が再び満たされるまで、部分文字列の前を再び前方に移動します。再び失敗するまで、端を前方に移動します。これが最初の部分文字列よりも短い部分文字列である場合は、仮の部分文字列としてマークします。
  • 最初の文字列全体を通過するまで繰り返します。
  • マークされた部分文字列が答えです。

ヒストグラム条件で使用するチェックを変更することにより、2番目の文字列として同じ文字セットまたは少なくとも各タイプの文字数。 (それはa[i]>0 && b[i]>0およびa[i]>=b[i]。)

満たそうとしているときにどの条件が満たされていないかを追跡し、それを壊そうとしているときにデクリメントするものだけをチェックする場合、ヒストグラムチェックを高速化できます。 (最初のビルドアップでは、満足したアイテムの数をカウントし、条件をfalseからtrueにする新しいキャラクターを追加するたびにカウントを増やします。)

33
Rex Kerr

動作中のコードなどの詳細を確認するには、次のブログ投稿を確認してください。

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

このアプローチを説明するために、例を使用します。string1= "acbbaca"およびstring2 = "aba"。ここでは、「ウィンドウ」という用語も使用します。これは、string1からの連続した文字ブロックを意味します(サブストリングという用語と交換できます)。

alt text

i)string1 = "acbbaca"およびstring2 = "aba"。

alt text

ii)最初の最小ウィンドウが見つかりました。 hasFound ['a'] == needToFind ['a'] == 2のように開始ポインターを進めることができないことに注意してください。進むとは、制約を破ることを意味します。

alt text

iii)2番目のウィンドウが見つかりました。開始ポインタはまだ最初の要素「a」を指しています。 hasFound ['a'](3)はneedToFind ['a'](2)よりも大きくなります。 hasFound ['a']を1つ減らし、開始ポインタを右に進めます。

alt text

iv)string2に見つからないため、「c」をスキップします。開始ポインタが「b」を指すようになりました。 hasFound ['b'](2)はneedToFind ['b'](1)よりも大きくなります。 hasFound ['b']を1つ減らし、開始ポインタを右に進めます。

alt text

v)開始ポインタが次の「b」を指すようになりました。 hasFound ['b'](1)はneedToFind ['b'](1)と同じです。すぐに停止し、これが新しく見つかった最小ウィンドウです。

このアイデアは主に、string1をトラバースする際の2つのポインター(ウィンドウの開始位置と終了位置)と2つのテーブル(needToFindとhasFound)の助けに基づいています。 needToFindは文字の総数をstring2に格納し、hasFoundはこれまでに出会った文字の総数を格納します。また、count変数を使用して、これまでに一致したstring2の合計文字を格納します(hasFound [x]がneedToFind [x]を超える文字はカウントしません)。 countがstring2の長さと等しい場合、有効なウィンドウが見つかったことがわかります。

終了ポインターを進める(要素xを指す)たびに、hasFound [x]を1つ増やします。 hasFound [x]がneedToFind [x]以下の場合も、カウントを1つ増やします。どうして?制約が満たされると(つまり、countがstring2のサイズに等しくなると)、制約を維持しながら開始ポインターを可能な限り右にすぐに進めます。

制約を維持しているかどうかを確認するにはどうすればよいですか? beginが要素xを指していると仮定して、hasFound [x]がneedToFind [x]よりも大きいかどうかを確認します。そうであれば、hasFound [x]を1つ減らし、制約を壊さずに開始ポインターを進めることができます。一方、そうでない場合は、開始ポインタがウィンドウの制約を破るとすぐに停止します。

最後に、最小ウィンドウ長が現在の最小値より短いかどうかを確認します。新しい最小値が見つかった場合、現在の最小値を更新します。

基本的に、アルゴリズムは制約を満たす最初のウィンドウを見つけてから、ずっと制約を維持し続けます。

39
1337c0d3r

O(n)ソリューション。基本的な考え方は単純です。各開始インデックスについて、部分文字列に必要な文字がすべて含まれるように、最小の終了インデックスを見つけます。終了インデックスは関数の過程で増加するため、データ構造を少しサポートするだけで、各文字を最大2回考慮します。

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
6
user287792

同じインタビューの質問を受けました。私は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");
}

Erlang[提供: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:

2
jackdaniel

これも見てください:

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

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;
}
//--------------------------------------------------------------------
1
Manish Kumar
    String s = "xyyzyzyx";
    String s1 = "xyz";
    String finalString ="";
    Map<Character,Integer> hm = new HashMap<>();
    if(s1!=null && s!=null && s.length()>s1.length()){
        for(int i =0;i<s1.length();i++){
            if(hm.get(s1.charAt(i))!=null){
                int k = hm.get(s1.charAt(i))+1;
                hm.put(s1.charAt(i), k);
            }else
                hm.put(s1.charAt(i), 1);
        }
        Map<Character,Integer> t = new HashMap<>();
        int start =-1;
         for(int j=0;j<s.length();j++){
             if(hm.get(s.charAt(j))!=null){
                 if(t.get(s.charAt(j))!=null){
                     if(t.get(s.charAt(j))!=hm.get(s.charAt(j))){
                     int k = t.get(s.charAt(j))+1;
                        t.put(s.charAt(j), k);
                     }
                 }else{
                     t.put(s.charAt(j), 1);
                     if(start==-1){
                         if(j+s1.length()>s.length()){
                             break;
                         }
                         start = j;
                     }
                 }
                 if(hm.equals(t)){
                    t = new HashMap<>();
                    if(finalString.length()<s.substring(start,j+1).length());
                    {
                        finalString=s.substring(start,j+1);
                    }
                    j=start;
                    start=-1;                       
                 }
             }
         }
0
Sai Chand

編集:どうやらO(n)アルゴリズムがあります。

残念ながら行きません... O(n)を取得できるか少し疑っています。明日チェックインして勝者を確認します;-)楽しんでください!

仮アルゴリズム
。 「これまでのベストマッチの長さ」の値を保持することにより、これを超える検索を中止できます。他のヒューリスティックを使用して、最適ではない(これまでの)ソリューションをさらに中止することができます。 str1の開始文字の順序の選択は非常に重要です。カウントが最も少ないstr1の文字から開始し、その後の試行でカウントが増加する他の文字で試すことをお勧めします。

  [loose pseudo-code]
  - get count for each letter/character in str1  (number of As, Bs etc.)
  - get count for each letter in str2
  - minLen = length(str1) + 1  (the +1 indicates you're not sure all chars of 
                                str2 are in str1)
  - Starting with the letter from string2 which is found the least in string1,
    look for other letters of Str2, in either direction of str1, until you've 
    found them all (or not, at which case response = impossible => done!). 
    set x = length(corresponding substring of str1).
 - if (x < minLen), 
         set minlen = x, 
         also memorize the start/len of the str1 substring.
 - continue trying with other letters of str1 (going the up the frequency
   list in str1), but abort search as soon as length(substring of strl) 
   reaches or exceed minLen.  
   We can find a few other heuristics that would allow aborting a 
   particular search, based on [pre-calculated ?] distance between a given
   letter in str1 and some (all?) of the letters in str2.
 - the overall search terminates when minLen = length(str2) or when 
   we've used all letters of str1 (which match one letter of str2)
   as a starting point for the search
0
mjv

Python3を使用して、O(N) Efficiency:

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
TheLogicGuy
//[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;
    }
}
0
Shashank

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
shlatchz

Java実装

public static String shortestSubstrContainingAllChars(String input, String target) {
    int needToFind[] = new int[256];
    int hasFound[] = new int[256];
    int totalCharCount = 0;
    String result = null;

    char[] targetCharArray = target.toCharArray();
    for (int i = 0; i < targetCharArray.length; i++) {
        needToFind[targetCharArray[i]]++;           
    }

    char[] inputCharArray = input.toCharArray();
    for (int begin = 0, end = 0; end < inputCharArray.length; end++) {

        if (needToFind[inputCharArray[end]] == 0) {
            continue;
        }

        hasFound[inputCharArray[end]]++;
        if (hasFound[inputCharArray[end]] <= needToFind[inputCharArray[end]]) {
            totalCharCount ++;
        }
        if (totalCharCount == target.length()) {
            while (needToFind[inputCharArray[begin]] == 0 
                    || hasFound[inputCharArray[begin]] > needToFind[inputCharArray[begin]]) {

                if (hasFound[inputCharArray[begin]] > needToFind[inputCharArray[begin]]) {
                    hasFound[inputCharArray[begin]]--;
                }
                begin++;
            }

            String substring = input.substring(begin, end + 1);
            if (result == null || result.length() > substring.length()) {
                result = substring;
            }
        }
    }
    return result;
}

これがJunitテストです

@Test
public void shortestSubstringContainingAllCharsTest() {
    String result = StringUtil.shortestSubstrContainingAllChars("acbbaca", "aba");
    assertThat(result, equalTo("baca"));

    result = StringUtil.shortestSubstrContainingAllChars("acbbADOBECODEBANCaca", "ABC");
    assertThat(result, equalTo("BANC"));

    result = StringUtil.shortestSubstrContainingAllChars("this is a test string", "tist");
    assertThat(result, equalTo("t stri"));
}
0
craftsmannadeem

これは素数を使用して1つのループを回避し、それを乗算に置き換えるアプローチです。他のいくつかのマイナーな最適化を行うことができます。

  1. 検索する任意の文字に一意の素数を割り当て、1興味のない文字に。

  2. 素数に必要な出現回数を掛けて、一致する文字列の積を求めます。現在、この製品は、同じ素因数が使用されている場合にのみ見つけることができます。

  3. 文字列を最初から検索し、実行中の製品に移動するときにそれぞれの素数を掛けます。

  4. 数値が正しい合計より大きい場合、最初の文字を削除し、実行中の製品からその素数を除算します。

  5. 数が正しい合計より小さい場合は、次の文字を含めて、実行中の製品に乗算します。

  6. 番号が正しい合計と同じ場合、一致が見つかった場合は、先頭と末尾を次の文字にスライドし、他の一致の検索を続けます。

  7. どのマッチが最も短いかを決定します。

要点

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
gauteh