Un de mes amis vient d'avoir son interview chez Google et a été rejeté car il ne pouvait pas donner de solution à cette question.
J'ai mon propre entretien dans quelques jours et je n'arrive pas à trouver un moyen de le résoudre.
Voici la question:
Vous recevez un modèle, tel que [a b a b]. Vous recevez également un chaîne, exemple "redblueredblue". J'ai besoin d'écrire un programme qui raconte si la chaîne suit le modèle donné ou non.
Quelques exemples:
Pattern: [a b b a] String: catdogdogcat renvoie 1
Pattern: [a b a b] String: redblueredblue renvoie 1
Modèle: [a b b a] Chaîne: redblueredblue renvoie 0
J'ai pensé à quelques approches, comme obtenir le nombre de caractères uniques dans le motif, puis trouver plusieurs sous-chaînes uniques de la chaîne, puis les comparer au motif à l'aide d'une table de hachage. Toutefois, cela s’avère problématique si la sous-chaîne de a fait partie de b.
Ce serait vraiment bien si l'un de vous pouvait m'aider avec ça. :)
METTRE À JOUR:
Ajout d'informations: le motif peut comporter un nombre quelconque de caractères (a-z). Deux caractères ne représenteront pas la même sous-chaîne. En outre, un caractère ne peut pas représenter une chaîne vide.
Ne vous contentez pas de traduire le motif en une expression rationnelle en utilisant des références arrières, c'est-à-dire quelque chose comme ceci (Python 3 avec le module "re" chargé):
>>> print(re.match('(.+)(.+)\\2\\1', 'catdogdogcat'))
<_sre.SRE_Match object; span=(0, 12), match='catdogdogcat'>
>>> print(re.match('(.+)(.+)\\1\\2', 'redblueredblue'))
<_sre.SRE_Match object; span=(0, 14), match='redblueredblue'>
>>> print(re.match('(.+)(.+)\\2\\1', 'redblueredblue'))
None
L'expression rationnelle est assez simple à générer. Si vous avez besoin de prendre en charge plus de 9 backrefs, vous pouvez utiliser des groupes nommés - voir le Python regexp docs .
La solution la plus simple à laquelle je puisse penser consiste à diviser la chaîne donnée en quatre parties et à comparer les différentes parties. Vous ne savez pas combien de temps a
ou b
est, mais les deux a
s sont de la même longueur que b
s. Le nombre de façons de diviser la chaîne donnée n’est donc pas très grand.
Exemple: Pattern = [a b a b]
, chaîne donnée = redblueredblue
(14 caractères au total)
|a|
(longueur de a
) = 1, cela donne 2 caractères pour a
s et 12 caractères sont laissés pour b
s, c'est-à-dire |b|
= 6. Chaîne divisée = r edblue r edblue
. Whoa, cela correspond tout de suite!|a| = 2, |b| = 5
-> chaîne divisée = re dblue re dblue
-> correspondanceExemple 2: Pattern = [a b a b]
, string = redbluebluered
(14 caractères au total)
|a| = 1, |b| = 6
-> chaîne divisée = r edblue b luered
-> pas de correspondance|a| = 2, |b| = 5
-> chaîne divisée = re dblue bl uered
-> pas de correspondance|a| = 3, |b| = 4
-> chaîne divisée = red blue blu ered
-> pas de correspondanceIl n'est pas nécessaire de vérifier le reste, car si vous avez basculé a
pour b
et vice versa, la situation est identique.
Quel est le motif qui a [a b c a b c]?
Voici la solution de backtracking Java. Lien source .
public class Solution {
public boolean isMatch(String str, String pat) {
Map<Character, String> map = new HashMap<>();
return isMatch(str, 0, pat, 0, map);
}
boolean isMatch(String str, int i, String pat, int j, Map<Character, String> map) {
// base case
if (i == str.length() && j == pat.length()) return true;
if (i == str.length() || j == pat.length()) return false;
// get current pattern character
char c = pat.charAt(j);
// if the pattern character exists
if (map.containsKey(c)) {
String s = map.get(c);
// then check if we can use it to match str[i...i+s.length()]
if (i + s.length() > str.length() || !str.substring(i, i + s.length()).equals(s)) {
return false;
}
// if it can match, great, continue to match the rest
return isMatch(str, i + s.length(), pat, j + 1, map);
}
// pattern character does not exist in the map
for (int k = i; k < str.length(); k++) {
// create or update the map
map.put(c, str.substring(i, k + 1));
// continue to match the rest
if (isMatch(str, k + 1, pat, j + 1, map)) {
return true;
}
}
// we've tried our best but still no luck
map.remove(c);
return false;
}
}
Ma mise en œuvre sur C #. Essayé de chercher quelque chose de propre en C #, impossible à trouver Donc, je vais l'ajouter à ici.
private static bool CheckIfStringFollowOrder(string text, string subString)
{
int subStringLength = subString.Length;
if (text.Length < subStringLength) return false;
char x, y;
int indexX, indexY;
for (int i=0; i < subStringLength -1; i++)
{
indexX = -1;
indexY = -1;
x = subString[i];
y = subString[i + 1];
indexX = text.LastIndexOf(x);
indexY = text.IndexOf(y);
if (y < x || indexX == -1 || indexY == -1)
return false;
}
return true;
}
Une autre solution de récursion en force brute:
import Java.io.IOException;
import Java.util.*;
public class Test {
public static void main(String[] args) throws IOException {
int res;
res = wordpattern("abba", "redbluebluered");
System.out.println("RESULT: " + res);
}
static int wordpattern(String pattern, String input) {
int patternSize = 1;
boolean res = findPattern(pattern, input, new HashMap<Character, String>(), patternSize);
while (!res && patternSize < input.length())
{
patternSize++;
res = findPattern(pattern, input, new HashMap<Character, String>(), patternSize);
}
return res ? 1 : 0;
}
private static boolean findPattern(String pattern, String input, Map<Character, String> charToValue, int patternSize) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < pattern.length(); i++) {
char c = pattern.charAt(i);
if (charToValue.containsKey(c)) {
sb.append(charToValue.get(c));
} else {
// new character in pattern
if (sb.length() + patternSize > input.length()) {
return false;
} else {
String substring = input.substring(sb.length(), sb.length() + patternSize);
charToValue.put(c, substring);
int newPatternSize = 1;
boolean res = findPattern(pattern, input, new HashMap<>(charToValue), newPatternSize);
while (!res && newPatternSize + sb.length() + substring.length() < input.length() - 1) {
newPatternSize++;
res = findPattern(pattern, input, new HashMap<>(charToValue), newPatternSize);
}
return res;
}
}
}
return sb.toString().equals(input) && allValuesUniq(charToValue.values());
}
private static boolean allValuesUniq(Collection<String> values) {
Set<String> set = new HashSet<>();
for (String v : values) {
if (!set.add(v)) {
return false;
}
}
return true;
}
}
class StringPattern{
public:
int n, pn;
string str;
unordered_map<string, pair<string, int>> um;
vector<string> p;
bool match(string pat, string str_) {
p.clear();
istringstream istr(pat);
string x;
while(istr>>x) p.Push_back(x);
pn=p.size();
str=str_;
n=str.size();
um.clear();
return dfs(0, 0);
}
bool dfs(int i, int c) {
if(i>=n) {
if(c>=pn){
return 1;
}
}
if(c>=pn) return 0;
for(int len=1; i+len-1<n; len++) {
string sub=str.substr(i, len);
if(um.count(p[c]) && um[p[c]].fi!=sub
|| um.count(sub) && um[sub].fi!=p[c]
)
continue;
//cout<<"str:"<<endl;
//cout<<p[c]<<" "<<sub<<endl;
um[p[c]].fi=sub;
um[p[c]].se++;
um[sub].fi=p[c];
um[sub].se++;
//um[sub]=p[c];
if(dfs(i+len, c+1)) return 1;
um[p[c]].se--;
if(!um[p[c]].se) um.erase(p[c]);
um[sub].se--;
if(!um[sub].se) um.erase(sub);
//um.erase(sub);
}
return 0;
}
};
Ma solution, étant donné que la table de hachage à deux faces est nécessaire et qu'il faut également compter le nombre de cartes de hachage
J'ai résolu ce problème en tant que problème de production linguistique en utilisant regexen.
def wordpattern( pattern, string):
'''
input: pattern 'abba'
string 'redbluebluered'
output: 1 for match, 2 for no match
'''
# assemble regex into something like this for 'abba':
# '^(?P<A>.+)(?P<B>.+)(?P=B)(?P=A)$'
p = pattern
for c in pattern:
C = c.upper()
p = p.replace(c,"(?P<{0}>.+)".format(C),1)
p = p.replace(c,"(?P={0})".format(C),len(pattern))
p = '^' + p + '$'
# check for a preliminary match
if re.search(p,string):
rem = re.match(p,string)
seen = {}
# check to ensure that no points in the pattern share the same match
for c in pattern:
s = rem.group(c.upper())
# has match been seen? yes, fail, no continue
if s in seen and seen[s] != c:
return 0
seen[s] = c
# success
return 1
# did not hit the search, fail
return 0
Plain Brute Force, je ne sais pas si une optimisation est possible ici ..
import Java.util.HashMap;
import Java.util.Map;
import org.junit.*;
public class Pattern {
private Map<Character, String> map;
private boolean matchInt(String pattern, String str) {
if (pattern.length() == 0) {
return str.length() == 0;
}
char pch = pattern.charAt(0);
for (int i = 0; i < str.length(); ++i) {
if (!map.containsKey(pch)) {
String val = str.substring(0, i + 1);
map.put(pch, val);
if (matchInt(pattern.substring(1), str.substring(val.length()))) {
return true;
} else {
map.remove(pch);
}
} else {
String val = map.get(pch);
if (!str.startsWith(val)) {
return false;
}
return matchInt(pattern.substring(1), str.substring(val.length()));
}
}
return false;
}
public boolean match(String pattern, String str) {
map = new HashMap<Character, String>();
return matchInt(pattern, str);
}
@Test
public void test1() {
Assert.assertTrue(match("aabb", "ABABCDCD"));
Assert.assertTrue(match("abba", "redbluebluered"));
Assert.assertTrue(match("abba", "asdasdasdasd"));
Assert.assertFalse(match("aabb", "xyzabcxzyabc"));
Assert.assertTrue(match("abba", "catdogdogcat"));
Assert.assertTrue(match("abab", "ryry"));
Assert.assertFalse(match("abba", " redblueredblue"));
}
}
@EricM
J'ai testé votre solution DFS et elle semble fausse, comme dans le cas suivant:
modèle = ["a", "b", "a"], s = "patrpatrr"
Le problème est que, lorsque vous rencontrez un modèle qui existe déjà dans dict et que vous trouvez qu'il ne peut pas contenir la chaîne suivante, vous supprimez et essayez de lui attribuer une nouvelle valeur. Cependant, vous n'avez pas vérifié ce modèle avec la nouvelle valeur pour les moments précédents.
Mon idée est de fournir un ajout (ou une fusion dans ce dict), une nouvelle valeur pour garder une trace de sa première apparition et une autre pile pour garder une trace du motif unique que je rencontre. Quand "pas de correspondance" se produit, je saurai qu'il y a un problème avec le dernier motif, je le sors de la pile et modifie la valeur correspondante dans le dict, je vais aussi recommencer à vérifier à l'index correspondant. Si ne peut plus être modifié. Je vais sauter jusqu'à ce qu'il n'en reste plus dans la pile, puis renvoyer False.
(Je veux ajouter des commentaires, mais je n'ai pas assez de réputation en tant que nouvel utilisateur. Je ne l'ai pas implémenté, mais jusqu'à présent, je n'ai trouvé aucune erreur dans ma logique. Je suis désolé s'il y a un problème avec ma solution. == Je vais essayer de le mettre en oeuvre plus tard.)
Si vous recherchez une solution en C++, voici une solution en force brute: https://linzhongzl.wordpress.com/2014/11/04/repeating-pattern-match/
function isMatch(pattern, str){
var map = {}; //store the pairs of pattern and strings
function checkMatch(pattern, str) {
if (pattern.length == 0 && str.length == 0){
return true;
}
//if the pattern or the string is empty
if (pattern.length == 0 || str.length == 0){
return false;
}
//store the next pattern
var currentPattern = pattern.charAt(0);
if (currentPattern in map){
//the pattern has alredy seen, check if there is a match with the string
if (str.length >= map[currentPattern].length && str.startsWith(map[currentPattern])){
//there is a match, try all other posibilities
return checkMatch(pattern.substring(1), str.substring(map[currentPattern].length));
} else {
//no match, return false
return false;
}
}
//the current pattern is new, try all the posibilities of current string
for (var i=1; i <= str.length; i++){
var stringToCheck = str.substring(0, i);
//store in the map
map[currentPattern] = stringToCheck;
//try the rest
var match = checkMatch(pattern.substring(1), str.substring(i));
if (match){
//there is a match
return true;
} else {
//if there is no match, delete the pair from the map
delete map[currentPattern];
}
}
return false;
}
return checkMatch(pattern, str);
}
pattern
- "abba"; input
- "redbluebluered"
pattern
, affectez à la liste pattern_count
. Ex .: [2,2]
pour a
et b
.pattern_lengths
pour chaque caractère unique. Ex .: [1,1]
.pattern_lengths
de droite à gauche en maintenant l'équation: pattern_count * (pattern_lengths)^T = length(input)
(produit scalaire des vecteurs). Utilisez step pour passer directement à la racine de l’équation suivante. pattern_lenghts
actuel (check_combination()
)Implémentation Python:
def check(pattern, input):
def _unique(pattern):
hmap = {}
for i in pattern:
if i not in hmap:
hmap[i] = 1
else:
hmap[i] += 1
return hmap.keys(), hmap.values()
def check_combination(pattern, string, pattern_unique, pattern_lengths):
pos = 0
hmap = {}
_set = set()
for code in pattern:
string_value = string[pos:pos + pattern_lengths[pattern_unique.index(code)]]
if code in hmap:
if hmap[code] != string_value:
return False
else:
if string_value in _set:
return False
_set.add(string_value)
hmap[code] = string_value
pos += len(string_value)
return False if pos < len(string) else True
pattern = list(pattern)
pattern_unique, pattern_count = _unique(pattern)
pattern_lengths = [1] * len(pattern_unique)
x_len = len(pattern_unique)
i = x_len - 1
while i>0:
diff_sum_pattern = len(input) - sum([x * y for x, y in Zip(pattern_lengths, pattern_count)])
if diff_sum_pattern >= 0:
if diff_sum_pattern == 0 and \
check_combination(pattern, input, pattern_unique, pattern_lengths):
return 1
pattern_lengths[i] += max(1, diff_sum_pattern // pattern_count[i])
else:
pattern_lengths[i:x_len] = [1] * (x_len - i)
pattern_lengths[i - 1] += 1
sum_pattern = sum([x * y for x, y in Zip(pattern_lengths, pattern_count)])
if sum_pattern <= len(input):
i = x_len - 1
else:
i -= 1
continue
return 0
task = ("abcdddcbaaabcdddcbaa","redbluegreenyellowyellowyellowgreenblueredredredbluegreenyellowyellowyellowgreenblueredred")
print(check(*task))
Sur l'exemple de modèle issu de ce code (20 caractères, 4 uniques), fonctionne 50000 fois plus rapidement que la bruteforce simple (DFS) avec récursivité (implémentation par @EricM); 30 fois plus rapide qu'une expression régulière (mise en œuvre par @IknoweD).
Je ne peux pas penser à beaucoup mieux que la solution de force brute: essayez chaque partitionnement possible de la Parole (c'est essentiellement ce que Jan a décrit).
La complexité d'exécution est O(n^(2m))
, où m
est la longueur du modèle et n
est la longueur de la chaîne.
Voici à quoi ressemble le code correspondant (j'ai demandé à mon code de renvoyer le mappage réel au lieu de 0 ou 1. Modifier le code pour renvoyer 0 ou 1 est simple):
import Java.util.Arrays;
import Java.util.ArrayDeque;
import Java.util.ArrayList;
import Java.util.Deque;
import Java.util.HashMap;
import Java.util.List;
import Java.util.Map;
public class StringBijection {
public static void main(String[] args) {
String chars = "abaac";
String string = "johnjohnnyjohnjohncodes";
List<String> stringBijection = getStringBijection(chars, string);
System.out.println(Arrays.toString(stringBijection.toArray()));
}
public static List<String> getStringBijection(String chars, String string) {
if (chars == null || string == null) {
return null;
}
Map<Character, String> bijection = new HashMap<Character, String>();
Deque<String> assignments = new ArrayDeque<String>();
List<String> results = new ArrayList<String>();
boolean hasBijection = getStringBijection(chars, string, 0, 0, bijection, assignments);
if (!hasBijection) {
return null;
}
for (String result : assignments) {
results.add(result);
}
return results;
}
private static boolean getStringBijection(String chars, String string, int charIndex, int stringIndex, Map<Character, String> bijection, Deque<String> assignments) {
int charsLen = chars.length();
int stringLen = string.length();
if (charIndex == charsLen && stringIndex == stringLen) {
return true;
} else if (charIndex == charsLen || stringIndex == stringLen) {
return false;
}
char currentChar = chars.charAt(charIndex);
List<String> possibleWords = new ArrayList<String>();
boolean charAlreadyAssigned = bijection.containsKey(currentChar);
if (charAlreadyAssigned) {
String Word = bijection.get(currentChar);
possibleWords.add(Word);
} else {
StringBuilder Word = new StringBuilder();
for (int i = stringIndex; i < stringLen; ++i) {
Word.append(string.charAt(i));
possibleWords.add(Word.toString());
}
}
for (String Word : possibleWords) {
int wordLen = Word.length();
int endIndex = stringIndex + wordLen;
if (endIndex <= stringLen && string.substring(stringIndex, endIndex).equals(Word)) {
if (!charAlreadyAssigned) {
bijection.put(currentChar, Word);
}
assignments.addLast(Word);
boolean done = getStringBijection(chars, string, charIndex + 1, stringIndex + wordLen, bijection, assignments);
if (done) {
return true;
}
assignments.removeLast();
if (!charAlreadyAssigned) {
bijection.remove(currentChar);
}
}
}
return false;
}
}