J'ai récemment rencontré une question intéressante sur les chaînes. Supposons qu'on vous donne ce qui suit:
Input string1: "this is a test string"
Input string2: "tist"
Output string: "t stri"
Ainsi, étant donné ci-dessus, comment puis-je aborder la recherche de la plus petite sous-chaîne de chaîne1 contenant tous les caractères de la chaîne 2?
Vous pouvez effectuer un balayage d'histogramme dans O(N+M)
time et O(1)
space où N
est le nombre de caractères de la première chaîne et M
est le nombre de caractères de la seconde.
Cela fonctionne comme ceci:
hist2[ s2[i] ]++
).Notez qu'en modifiant le contrôle que vous utilisez dans la condition d'histogramme, vous pouvez choisir de définir le même jeu de caractères comme deuxième chaîne ou d'utiliser au moins autant de caractères de chaque type. (C'est juste la différence entre a[i]>0 && b[i]>0
et a[i]>=b[i]
.)
Vous pouvez accélérer les vérifications d'histogramme si vous gardez une trace de la condition non satisfaite lorsque vous essayez de la satisfaire, et ne cochez que ce que vous décrémentez lorsque vous essayez de la décomposer. (Lors de la création initiale, vous comptez le nombre d'éléments que vous avez satisfaits et vous incrémentez à chaque fois que vous ajoutez un nouveau caractère qui passe de la condition false à true.)
Pour plus de détails, y compris le code de travail, consultez mon blog sur:
http://www.leetcode.com/2010/11/finding-minimum-window-in-s-which.html
Pour illustrer cette approche, j’utilise un exemple: string1 = "acbbaca"
et string2 = "aba"
. Ici, nous utilisons également le terme "fenêtre", ce qui signifie un bloc contigu de caractères de chaîne1 (pourrait être échangé avec le terme sous-chaîne).
i) string1 = "acbbaca" et string2 = "aba".
ii) La première fenêtre minimale est trouvée . Notez que nous ne pouvons pas avancer commencer pointeur comme hasFound ['a'] == needToFind ['a'] == 2. Avancer serait signifie briser la contrainte.
iii) La deuxième fenêtre est trouvée. commencer le pointeur pointe toujours sur le premier élément 'a'. hasFound ['a'] (3) est supérieur à needToFind ['a'] (2). Nous décrémente hasFound ['a'] de un et avance le pointeur vers la droite.
iv) On saute 'c' car on ne le trouve pas dans string2. Le pointeur de début pointe maintenant sur 'b' . hasFound ['b'] (2) est supérieur à needToFind ['b'] (1). Nous décrémentons hasFound ['b'] par un et avance commence pointeur à droite.
v) Le pointeur de début pointe maintenant sur le prochain 'b'. hasFound ['b'] (1) est égal à avoir besoin de ToFind ['b'] (1). Nous nous arrêtons immédiatement et ceci est notre nouvellement trouvé fenêtre minimum.
L'idée est principalement basée sur l'aide de deux pointeurs (position de début et de fin de la fenêtre) et de deux tables (needToFind et hasFound) tout en parcourant string1. needToFind stocke le nombre total de caractères dans string2 et hasFound enregistre le nombre total de caractères déjà rencontrés. Nous utilisons également une variable de comptage pour stocker le nombre total de caractères dans la chaîne2 respectés jusqu'à présent (sans compter les caractères où hasFound [x] dépasse needToFind [x]). Lorsque count est égal à la longueur de string2, nous savons qu'une fenêtre valide a été trouvée.
Chaque fois que nous avançons le pointeur de fin (en pointant sur un élément x), nous incrémentons hasFound [x] de un. Nous incrémentons également d'un nombre si hasFound [x] est inférieur ou égal à needToFind [x]. Pourquoi? Lorsque la contrainte est remplie (c'est-à-dire que le nombre est égal à la taille de string2), nous faisons immédiatement avancer le pointeur begin le plus à droite possible tout en maintenant la contrainte.
Comment vérifions-nous s'il maintient la contrainte? Supposons que begin pointe sur un élément x, nous vérifions si hasFound [x] est supérieur à needToFind [x]. Si c'est le cas, nous pouvons décrémenter hasFound [x] de un et avancer le pointeur de début sans rompre la contrainte. D'autre part, si ce n'est pas le cas, nous nous arrêtons immédiatement car l'avancement du pointeur de départ rompt la contrainte de fenêtre.
Enfin, nous vérifions si la longueur minimale de la fenêtre est inférieure au minimum actuel. Mettez à jour le minimum actuel si un nouveau minimum est trouvé.
Pour l’essentiel, l’algorithme trouve la première fenêtre qui satisfait à la contrainte, puis continue à la maintenir.
Voici une solution O(n). L'idée de base est simple: pour chaque index de départ, recherchez le dernier indice de fin, de sorte que la sous-chaîne contienne toutes les lettres nécessaires. L'astuce est que le dernier indice de fin augmente au cours de la fonction. Ainsi, avec un petit support de structure de données, nous considérons chaque caractère au plus deux fois.
En 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
J'ai reçu la même question d'entrevue. Je suis un candidat C++ mais j'étais en mesure de coder relativement vite en Java.
Java [Gracieuseté de 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++ [Gracieuseté de 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 [Gracieuseté de: 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:
S'il vous plaît jeter un oeil à cela aussi:
//-----------------------------------------------------------------------
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;
}
//--------------------------------------------------------------------
Edit : apparemment, il existe un algorithme O(n) (voir la réponse de l'algorithmiste). Évidemment, cela va battre la ligne de base [naïve] décrite ci-dessous!
Dommage que je dois y aller ... Je suis un peu méfiant sur le fait que nous pouvons obtenir O (n). Je vérifierai demain pour voir le gagnant ;-) Amusez-vous!
Algorithme provisoire :
L'idée générale est d'essayer séquentiellement d'utiliser un caractère de str2 trouvé dans str1 comme début de recherche (dans un ou les deux sens) de toutes les autres lettres de str2. En conservant la valeur de "longueur de la meilleure correspondance jusqu'à présent", nous pouvons interrompre les recherches lorsqu'elles dépassent cette valeur. D'autres heuristiques peuvent probablement être utilisées pour abandonner davantage les solutions sous-optimales (jusqu'à présent). Le choix de l'ordre des lettres de départ dans str1 compte beaucoup; il est suggéré de commencer par la ou les lettres de str1 qui ont le compte le plus bas et d'essayer avec les autres lettres, un compte croissant, lors des tentatives suivantes.
[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
Je l'ai implémenté en utilisant Python3 à O(N) efficacité:
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"
//[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;
}
}
Implémentation 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;
}
Voici l'implémentation 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;
}
Voici le test 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"));
}
Cette approche utilise des nombres premiers pour éviter une boucle et la remplacer par des multiplications. Plusieurs autres optimisations mineures peuvent être effectuées.
Attribuez un nombre premier unique à l'un des caractères que vous souhaitez rechercher et 1
aux caractères sans intérêt.
Trouvez le produit d'une chaîne correspondante en multipliant le nombre premier par le nombre d'occurrences qu'il devrait avoir. Maintenant, ce produit ne peut être trouvé que si les mêmes facteurs premiers sont utilisés.
Effectuez une recherche dans la chaîne depuis le début en multipliant le nombre premier correspondant lorsque vous accédez à un produit en cours d'exécution.
Si le nombre est supérieur à la somme correcte, supprimez le premier caractère et divisez son nombre premier du produit en cours d'exécution.
Si le nombre est inférieur à la somme correcte, incluez le caractère suivant et multipliez-le dans votre produit en cours d'exécution.
Si le nombre correspond à la somme correcte, vous avez trouvé une correspondance, faites glisser le début et la fin du caractère suivant et poursuivez la recherche d'autres correspondances.
Décidez lequel des matchs est le plus court.
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))
(Remarque: cette réponse a été envoyée à une question en double, la réponse originale est maintenant supprimée.)