检查给定的字符串是否符合给定的模式

22

我的朋友刚刚在 Google 面试时被拒绝了,因为他不能给出这个问题的解决方案。

我自己也要在几天后面试,但似乎无法想出解决方法。

以下是问题:

给定一个模式,例如 [a b a b]。还给定一个字符串,例如 "redblueredblue"。我需要编写一个程序来告诉我字符串是否遵循给定的模式。

一些例子:

模式:[a b b a] 字符串:catdogdogcat 返回 1

模式:[a b a b] 字符串:redblueredblue 返回 1

模式:[a b b a] 字符串:redblueredblue 返回 0

我想到了一些方法,比如获取模式中唯一字符的数量,然后找到那么多个唯一的子字符串,再使用哈希映射表将其与模式进行比较。但是,如果 a 的子字符串是 b 的一部分,这种方法就有问题了。

如果你们中任何人能帮助我解决这个问题,那就太好了。:)

更新:

添加信息:模式中可以有任意数量的字符(a-z)。两个字符不会代表相同的子字符串。并且,一个字符不能表示空字符串。


模式有什么限制吗?它只是任意顺序的符号组合吗? - Alexandru Barbarosie
1
在模式中匹配特定字母的任何字符串都可以为空吗? - templatetypedef
此外,模式字符串中可能有多少个字符?它总是由a和b组成,还是可以有更多的字符? - templatetypedef
模式中可以有任意数量的字符。两个字符不会表示相同的子字符串。此外,一个字符不能表示空字符串。 - shashankg77
1
存在一种朴素的解决方案,即枚举字符串的所有子串分区,并检查它是否与模式字符串匹配,但这可能需要指数时间来匹配模式字符串的长度。我想知道是否存在根本更快的方法,或者是否已知此问题是NP完全的(或co-NP完全的?) - templatetypedef
16个回答

19
我能想到的最简单的解决方案是将给定的字符串分成四个部分,并比较各个部分。您不知道 ab 的长度,但两个 a 长度相同,两个b长度也相同。 因此,给定字符串的划分方式并不是很多。

例如:
模式 = [a b a b] ,给定字符串 = redblueredblue(总共14个字符)

  1. |a|a的长度)= 1,则为 a 分配2个字符,剩下12个字符供 b 使用,即|b|= 6。 划分后的字符串= r edblue r edblue。哇,这立刻就匹配了!
  2. (仅出于好奇) |a| = 2, |b| = 5 -> 划分后的字符串= re dblue re dblue -> 匹配

示例2:
模式 = [a b a b] ,给定字符串 = redbluebluered(总共14个字符)

  1. |a| = 1, |b| = 6 -> 划分后的字符串= r edblue b luered -> 不匹配
  2. |a| = 2, |b| = 5 -> 划分后的字符串= re dblue bl uered -> 不匹配
  3. |a| = 3, |b| = 4 -> 划分后的字符串= red blue blu ered -> 不匹配

剩下的部分无需检查,因为如果您将 ab 互换位置,则情况是相同的。

[a b c a b c] 的模式是什么?


但是对于像[a a b b][b b a a]这样的模式,它会起作用吗? - user7098526

10

你只需要使用回溯引用把这个模式转换成正则表达式,例如(在已加载“re”模块的 Python 3 中):

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

正则表达式看起来相当简单易懂。如果您需要支持多于9个反向引用,您可以使用命名组 - 请参阅Python正则表达式文档


这个解决方案的时间复杂度是多少?我担心这可能会起作用,但潜在地不比蛮力方法快。 - templatetypedef
1
如果我想让这两个名称组的字符串值是唯一的,该怎么办? - shashankg77
@EricM,如果不允许使用正则表达式,那么应该采取什么方法? - Timothy Ha
@TimothyHa,像这个粗略的解决方案一样实现一个深度优先搜索:https://gist.github.com/EricMountain/51cb333297ef37230582。 - EricM
尝试运行上述Gist链接,似乎对某些情况给出了错误的输出。这是我受你启发后的答案:https://gist.github.com/alexsapps/83e0054672973672f39e - Alexander Taylor
哇,不知道我怎么能够推送一个连编译都不能通过的版本。所以我修复了它,使其也能够与你的测试用例一起工作:https://gist.github.com/EricMountain/51cb333297ef37230582。 - EricM

2

这里是一个Java回溯解决方案。源链接

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;
 }

}

1

我使用正则表达式解决了这个语言生成问题。

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

1
一种更加暴力的递归解决方案:

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;
    }
}

1

我在 C# 上实现了这个功能。尝试寻找一个干净的解决方案,但没有找到。所以我会将它添加到这里。

   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;

    }

看起来你正在实现完全不同的东西。 - apatsekin

0
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;
  }
};

我的解决方案需要两个侧面的哈希映射,并且还需要计算哈希映射的数量


0

我的 JavaScript 解决方案:

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);

}


你的代码出现了错误。你的检查 str.length >= map[currentPattern].length && str.startsWith(map[currentPattern]) 不成立。请仔细检查并修正解决方案。 - Ogunleye Olawale

0

Python解决方案基于Java解决方案,链接如下:https://www.algo.monster/problems/word_pattern_ii

def helper(pattern, s, idxPattern, idxString, myMap, mySet):
    if (idxPattern == len(pattern)) and (idxString == len(s)):
        return True
    if (idxPattern >= len(pattern)) or (idxString >= len(s)):
        return False
    thisChar = pattern[idxPattern]
    #print ("At Char: ", thisChar, " at location: ", idxPattern)
    for idxK in range(idxString + 1, len(s) + 1):
        subString = s[idxString:idxK]
        if (thisChar not in myMap) and (subString not in mySet) :
            myMap[thisChar] = subString
            mySet.add(subString)
            # print ("Before Map {0}, Set: {1}".format(myMap, mySet))       
            if helper(pattern, s, idxPattern + 1, idxK, myMap, mySet):
                return True
            myMap.pop(thisChar)
            mySet.remove(subString)
            # print ("After Map {0}, Set: {1}".format(myMap, mySet))      
        elif (thisChar in myMap) and (myMap[thisChar] == subString):
            if helper(pattern, s, idxPattern + 1, idxK, myMap, mySet):
                return True    
      
def word_pattern_match(pattern: str, s: str) -> bool:
    # WRITE YOUR BRILLIANT CODE HERE
    print ("Pattern {0}, String {1}".format(pattern, s))
    if (len(pattern) == 0) and (len(s) == 0):
        return True
    if (len(pattern) == 0):
        return False
    myMap = dict()
    mySet = set()

    return helper(pattern, s, 0, 0, myMap, mySet)

if __name__ == '__main__':
    pattern = input()
    s = input()
    res = word_pattern_match(pattern, s)
    print('true' if res else 'false')

0

我想不出比暴力解决方案更好的了:尝试单词的每种可能的分割(这本质上就是Jan所描述的)。

运行时间复杂度为O(n^(2m)),其中m是模式的长度,n是字符串的长度。

以下是代码示例(我让我的代码返回实际映射而不仅仅是0或1。将代码修改为返回0或1很容易):

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;
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接