最近在一次采访中有人问我这个问题:
“给出一个模式和一个字符串输入-查找字符串是否跟随 相同的模式并返回true或false。“
示例:
- 模式:“ abba”,输入:“ redbluebluered”应返回1。
- 模式:“ aaaa”,输入:“ asdasdasdasd”应返回1。
- 模式:“ aabb”,输入:“ xyzabcxzyabc”应返回0。
我可以考虑使用正则表达式,但是我们需要不使用正则表达式。没有正则表达式的暴力方式是什么?还有什么更有效的方法吗?
如果可以,有人可以详细地解释我暴力还是有效的方式来解决这个问题?我认为这是一个非常困难的问题。
答案 0 :(得分:1)
以递归方式进行操作更容易。
在每个步骤中,我们都有一个要匹配的模式,一个要匹配的字符串以及我们已经分配的char到字符串的映射:
// initially, map should be empty. if this method returns true,
// map will contained the successful char-to-string mapping
boolean solve(String ptrn, String str, Map<Character, String> map) {
// if pattern is empty, string must also be empty
if (ptrn.length() == 0) {
return str.length() == 0;
}
char c = ptrn.charAt(0);
if (map.containsKey(c)) {
// first char of the pattern is alrady assigned
// the string must begin with the mapped substring
String substitution = map.get(c);
if (str.startsWith(substitution)) {
// chop off the assigned substring and try to match the rest of the string
return solve(ptrn.substring(1), str.substring(substitution.length()), map);
} else {
// the current assignment map is impossible. fail!
return false;
}
}
// first char of the pattern is not assigned
// loop over the string and try assigning substrings
for (int i = 1; i <= str.length(); i++) {
// assign new mapping and try to parse with the new assignment in place
map.put(c, str.substring(0, i));
if (solve(ptrn, str, map)) {
return true;
}
// assignment failed. remove it.
map.remove(c);
}
return false;
}
通过操作索引而不是子字符串,并用循环替换某些递归,当然可以提高效率。
答案 1 :(得分:1)
如果您知道与每个模式字符相对应的子字符串的长度,则检查字符串非常容易。
有效实施的诀窍似乎是使您需要检查的长度分配数量最少。
对于每个模式和测试字符串,都有一个线性Diophantine方程式限制了可能的长度分配:
模式aaaaa的字符串长度为16⇒4a = 16⇒a = 4
字符串长度为14⇒2a + 2b = 14⇒a + b = 7的模式abba
等
在大多数情况下,对正整数长度的要求将极大地限制可能的长度分配的数量,因此,我对此问题的第一个尝试是枚举这些可能的分配并检查每个分配。
仍然有多种输入(带有长字符串的大量模式字母)导致指数复杂,但是...但是这里的所有其他有效答案(当前)都使该问题更严重。
答案 2 :(得分:0)
更新:根据我看到的其他答案,我的解决方案显然不是最佳的。尽管这还不完整,但我认为有必要将其与其他答案进行比较,因此我将其留在此处。
似乎这个问题打算在字符串中找到任何重复的字符串。因此,我认为这里的首要任务是确定字符串中的哪些块代表了我们正在寻找的模式的单位。
在“ redbluebluered”的情况下,这些块是“ red”和“ blue”。
Set<String> findRepeatedStringsWithinString(String input) {
Set<String> setOfStrings = new HashSet<String>();
List<String> listOfStrings = new ArrayList<String>();
for(int i = 0; i < input.length(); i++) {
for(int j = 0; j < input.length() - i; j++) {
String subString = input.substring(i,j);
setOfStrings.add(subString);
listOfStrings.add(subString);
}
}
//At this point, I've got a Set of all unique substrings within the input and a List of all substrings that contains lots of duplicates.
//I need to remove all of the unique Strings from the Set that don't have repeating values in the List for starters.
//I'll end up with a Set that I think looks like this:
// r, e, d, b, l, u, re, bl, ue, ed, red, blu, blue
setOfStrings = removeAllUniqueStringsInListFromSet(listOfStrings, setOfStrings);
//I've noticed that each of these units is as large as possible, so any String that is a subString within my Set seems valid to remove. I would confirm this with the interviewer.
setOfString = removeSubStringsFromSet(setOfStrings);
//Set = red, blue
//From here, it's a matter of more String comparisons between this Set and the pattern, but other people are producing higher quality algorithms than mine and it's late. I don't think that continuing on this line of reasoning will get either of us the job, but I'll leave this here for others to compare.
}
Set<String> removeAllUniqueStringsInListFromSet(List<String> listOfStrings, Set<String> setOfStrings) {
Map<String, Integer> mapOfStrings = new HashMap<>();
for(String possiblyUniqueString : listOfStrings) {
if(mapOfStrings.get(possiblyUniqueString)) {
mapOfStrings.put(possiblyUniqueString, mapOfStrings.get(possiblyUniqueString) + 1);
} else {
mapOfStrings.put(possiblyUniqueString, 1);
}
}
//With this map of number of occurrences of each String in the List, I can now figure out which Strings are unique and remove them.
for(String key : mapOfStrings.keySet()) {
if(mapOfStrings.get(key) == 1) {
setOfStrings.remove(key);
}
}
return setOfStrings;
}
Set<String> removeSubStringsFromSet(Set<String> setOfStrings) {
//This is me sucking at dealing with Sets, sorry
List<String> listOfStrings = new ArrayList<>(setOfStrings);
for(String string : listOfStrings) {
for(int i = 0; i < listOfStrings.size(); i++) {
if(!listOfStrings.get(i).equals(string) && listOfStrings.get(i).contains(string)) {
setOfString.remove(string);
}
}
}
return setOfStrings;
}
答案 3 :(得分:0)
这是我的解决方法。
public class App {
public static void main(String args[]) {
System.out.println(isMatch("abba","redbluebluered" ));
System.out.println(isMatch("aaaa","asdasdasdasd" ));
System.out.println(isMatch("aabb","xyzabcxzyabc" ));
}
static boolean isMatch(String patternString, String dataString) {
List pattern = toList(patternString);
List data = toList(dataString);
return isMatch(pattern, data, new HashMap<>());
}
private static boolean isMatch(List pattern, List data, Map<Character, List> matches) {
if (data == null) {
return pattern == null;
}
if (matches.containsKey(pattern.c)) {
List list = matches.get(pattern.c);
List tmp = data;
while (list != null) {
if (tmp == null) {
return false;
}
if (list.c != tmp.c) {
return false;
}
tmp = tmp.next;
list = list.next;
}
return isMatch(pattern.next, tmp, matches);
}
List tmp = data;
List partialMatchHead;
List partialMatchTail;
partialMatchHead = new List();
partialMatchHead.c = tmp.c;
tmp = tmp.next;
partialMatchTail = partialMatchHead;
while (tmp != null) {
Map<Character, List> map = new HashMap<>(matches);
map.put(pattern.c, partialMatchHead);
if (isMatch(pattern.next, tmp, map)) {
return true;
}
partialMatchTail.next = new List();
partialMatchTail.next.c = tmp.c;
partialMatchTail = partialMatchTail.next;
tmp = tmp.next;
}
return false;
}
private static List toList(String string) {
List head = new List();
head.c = string.charAt(0);
List current = head;
for (int i = 1; i < string.length(); i++) {
List tmp = new List();
tmp.c = string.charAt(i);
current.next = tmp;
current = tmp;
}
return head;
}
static class List {
char c;
List next;
}
}
此解决方案等效于@Misha解决方案。
PS :Python here
中存在相同的问题答案 4 :(得分:0)
好问题。我尝试了一种没有递归的简单方法。 首先,我检查模式中存在哪些字母。然后,我创建输入的所有子字符串。在第三步中,我创建了模式字母和子字符串的所有组合。最后,我测试所有这些组合。 效率不高,但是可以。
public static int matches(String pattern, String example) {
// 1. which pattern-letters exist?
Set<String> set = new HashSet<>();
for (int i = 0; i < pattern.length(); i++) {
set.add(pattern.substring(i, i + 1));
}
// 2. which substrings of example exist?
Set<String> substrings = new HashSet<>();
for (int i = 0; i < (example.length() - 1); i++) {
for (int j = i + 1; j < example.length(); j++) {
substrings.add(example.substring(i, j));
}
}
// 3. create all combinations
List<Map<String, String>> list = new ArrayList<>();
for (String s : set) {
List<Map<String, String>> l = new ArrayList<>();
for (String x : substrings) {
if (list.isEmpty()) {
Map<String, String> map = new HashMap<>();
map.put(s, x);
l.add(map);
} else {
for (Map<String, String> map : list) {
Map<String, String> map2 = new HashMap<>();
for (Entry<String, String> e : map.entrySet()) {
map2.put(e.getKey(), e.getValue());
}
map2.put(s, x);
l.add(map2);
}
}
}
list.addAll(l);
}
// 4. try all combinations
for (Map<String, String> map : list) {
String match = "";
for (int i = 0; i < pattern.length(); i++) {
match += map.get(pattern.substring(i, i + 1));
}
if (example.equals(match)) {
return 1;
}
}
return 0;
}
答案 5 :(得分:0)
我将按以下方式进行处理:
考虑模式中的第一个字符(第一个示例中为'a')
因为到目前为止还未知'a',所以假设模式'a'的长度(最初为1,对应于“ r”)
移动到模式('b')中的下一个字符
由于到目前为止尚不知道“ b”,因此假设模式“ b”的长度(最初为“ 1”,对应于“ e”)
移动到模式('b')中的下一个字符
因为已知“ b”为“ e”,所以您可以与下一个块“ d”进行比较。在这里,我们失败了,我们需要回溯到模式“ b”的第一次出现,并使用下一个长度(2,3,...,直到没有更多空间)重试。
如果'b'找不到匹配项,则回溯到第一个出现的'a'。
此过程一直进行到字符串用尽或找到所有模式的匹配项为止。
可以通过以输入为输入的递归过程来实现
如果尚未假设当前模式,请为该模式形成第一个假设(字符串中的下一个字符),然后继续搜索。请注意记录对此模式进行假设的递归级别。
如果已经假设了当前模式,请从当前位置检查字符串是否匹配。如果存在匹配项,则除非您位于字符串的末尾并完成操作,否则以递归方式继续搜索。
如果没有匹配项,并且这是该模式的第一个匹配条件(即递归深度等于记录的递归深度),则增加该长度以再次进行假设并继续搜索。
>如果没有匹配项,并且这不是该模式的首次出现,请返回不匹配的模式的标识。 (执行递归调用时,如果返回的值指示失败,则将返回的模式标识与当前的模式标识进行比较,如果不同则将其返回给调用方。)
'a', 'a'= "r", "edbluebluered"
'b', 'a'= "r", 'b'= "e", "dbluebluered" => mismatch "e" <-> "d"
'b', 'a'= "r", 'b'= "ed", "bluebluered" => mismatch "ed" <-> "bl"
'b', 'a'= "r", 'b'= "edb", "luebluered" => mismatch "edb" <-> "lue"
...
'a', 'a'= "re", "dbluebluered"
'b', 'a'= "re", 'b'= "d", "bluebluered" => mismatch "d" <-> "b"
'b', 'a'= "re", 'b'= "db", "luebluered" => mismatch "db" <-> "lu"
...
'a', 'a'= "red", "bluebluered"
'b', 'a'= "red", 'b'= "b", "luebluered" => mismatch "b" <-> "l"
'b', 'a'= "red", 'b'= "bl", "uebluered" => mismatch "bl" <-> "ue"
'b', 'a'= "red", 'b'= "blu", "ebluered" => mismatch "blu" <-> "ebl"
'b', 'a'= "red", 'b'= "blue", "bluered"
'b', 'a'= "red", 'b'= "blue", "red"
'b', 'a'= "red", 'b'= "blue", "" done !
可以通过检查字符串尾部的长度并将其与当前模式的长度进行比较来简单地实现条件“没有更多的空间”,但是可以通过对所有假设的模式的长度进行累加来积累更好的估计值。模式字符串。例如,如果我们假设'a'=“ bl”和'b'=“ uer”,则模式字符串“ abba”预测长度至少为10。