【问题标题】:Finding the Number of Times an Expression Occurs in a String Continuously and Non Continuously查找表达式在字符串中连续和非连续出现的次数
【发布时间】:2015-07-09 00:41:37
【问题描述】:

我通过电话进行了一次编程面试,并被问到这个问题:

给定一个字符串(例如):

“aksdbaalaskdhfbblajdfhacccc aoudgalsaa bblisdfhcccc”

和一个表达式(例如):

"a+b+c-"

地点:

+:表示重复2次之前的字符

-:表示重复4次之前的字符

求给定表达式在字符串中出现操作数非连续和连续出现的次数。

上面的表达式出现了4次:

1) aksdbaalaskdhfbblajdfhacccc aoudgalsaa bblisdfhcccc
        ^^       ^^       ^^^^                    
        aa       bb       cccc
2) aksdbaalaskdhfbblajdfhacccc aoudgalsaa bblisdfhcccc
        ^^       ^^                               ^^^^
        aa       bb                               cccc

3) aksdbaalaskdhfbblajdfhacccc aoudgalsaa bblisdfhcccc
        ^^                                ^^      ^^^^
        aa                                bb      cccc

4) aksdbaalaskdhfbblajdfhacccc aoudgalsaa bblisdfhcccc
                                       ^^ ^^      ^^^^
                                       aa bb      cccc

我不知道该怎么做。我开始使用带有大量索引标记的迭代蛮力方法,但意识到在中途编码会多么混乱和困难:

import java.util.*;

public class Main {

    public static int count(String expression, String input) {
        int count = 0;
        ArrayList<char[]> list = new ArrayList<char[]>();

        // Create an ArrayList of chars to iterate through the expression and match to string
        for(int i = 1; i<expression.length(); i=i+2) {
            StringBuilder exp = new StringBuilder();
            char curr = expression.charAt(i-1);
            if(expression.charAt(i) == '+') {
                exp.append(curr).append(curr);
                list.add(exp.toString().toCharArray());
            }
            else { // character is '-'
                exp.append(curr).append(curr).append(curr).append(curr);
                list.add(exp.toString().toCharArray());
            }
        }

        char[] inputArray = input.toCharArray();
        int i = 0; // outside pointer
        int j = 0; // inside pointer
        while(i <= inputArray.length) {
            while(j <= inputArray.length) {
                for(int k = 0; k< list.size(); k++) {
                    /* loop through 
                     * all possible combinations in array list
                     * with multiple loops
                     */
                }
                j++;
            }
            i++;
            j=i;
        }
        return count;
    }

    public static void main(String[] args) {
        String expression = "a+b+c-";
        String input = "aaksdbaalaskdhfbblajdfhacccc aoudgalsaa bblisdfhcccc";
        System.out.println("The expression occurs: "+count(expression, input)+" times");
    }
}

在花了很多时间迭代之后,他提到了递归,但我仍然看不到递归的明确方法,我无法解决这个问题。我现在正在尝试在面试后解决它,但仍然不确定如何解决这个问题。我应该如何解决这个问题?解决方案明显吗?我认为这对于编码电话面试来说是一个非常难的问题。

【问题讨论】:

  • 我也想过暴力破解它。但我会尝试以我的方式去做。。
  • 这是一个很难的问题吗?还是只有我?
  • @Kingsley 不,这并不难。或者您想要工作和测试过的代码?
  • @Sasha Salauyou 我以为面试题很难,我想我只需要多练习!
  • @Kingsley 是的,只是练习。如果您对我的回答有什么不明白的地方,请追问。顺便说一句,这个时间限制是多少?

标签: java regex string algorithm expression


【解决方案1】:

递归可能如下(伪代码):

int search(String s, String expression) {

     if expression consists of only one token t /* e. g. "a+" */ {
         search for t in s
         return number of occurrences
     } else {
         int result = 0
         divide expression into first token t and rest expression
         // e. g. "a+a+b-" -> t = "a+", rest = "a+b-"
         search for t in s
         for each occurrence {
             s1 = substring of s from the position of occurrence to the end
             result += search(s1, rest) // search for rest of expression in rest of string
         }
         return result
     }
}   

将此应用于整个字符串,您将获得非连续出现的次数。要获得连续出现,您根本不需要递归 - 只需将表达式转换为字符串并通过迭代搜索即可。

【讨论】:

  • 在这一步你会做很多重复的搜索。 search for t in s 与兄弟节点进行了相同的搜索。
  • @kr.pradeep 不,为什么?假设输入 = aabbaabb,exp = aa, bb。在这里,我出现了 3 次。现在。调用初始字符串:search("aabbaabb", "aa, bb")。它搜索“aa”,找到第一个“aa”,然后调用search("bbaabb", "bb")(返回2,因为“bb”是在“bbaabb”中出现两次的1-token表达式)。 result = 0 + 2 = 2。然后它找到第二个“aa”并调用search("bb", "bb"),它返回1.result = 2 + 1 = 3。结束。
  • @kr.pradeep 我称的不是整个表达式和整个字符串。在递归调用中,我传递 rest of expressionrest of a stringsearchFromIndexcurrentTokenIndex 也在做同样的事情
  • 您在方法中执行传递字符串的子字符串并搜索所有出现的标记,这是重复搜索。在上面的示例中,您最后一次 bb 搜索了 2 次。
  • @kr.pradeep 在示例 OP 中提供的最后一个“cccc”被找到 3 次并且参与了 3 次出现(共 4 次)。你不称它为“重复”吗?
【解决方案2】:

如何预处理 aksdbaalaskdhfbblajdfhacccc aoudgalsaa bblisdfhcccc?

这变成a1k1s1d1b1a2l1a1s1k1d1h1f1b2l1a1j1d1f1h1a1c4a1o1u1d1g1a1l1s1a2b2l1i1s1d1f1h1c4

现在查找出现的 a2、b2、c4。

【讨论】:

  • "aaaa" 是否应该匹配 "a+a+"?
  • 如果您将搜索标记 a+b+c- 预处理为 aa、bb、cccc 并现在在输入字符串中搜索这些标记会更好。
【解决方案3】:

尝试了下面的代码,但现在它只给出了基于深度优先的第一个可能匹配。

需要进行更改以进行所有可能的组合,而不仅仅是第一个

import java.util.ArrayList;
import java.util.List;

public class Parsing {
    public static void main(String[] args) {
        String input = "aksdbaalaskdhfbblajdfhacccc aoudgalsaa bblisdfhcccc";
        System.out.println(input);

        for (int i = 0; i < input.length(); i++) {
            System.out.print(i/10);
        }
        System.out.println();

        for (int i = 0; i < input.length(); i++) {
            System.out.print(i%10);
        }
        System.out.println();

        List<String> tokenisedSearch = parseExp("a+b+c-");
        System.out.println(tokenisedSearch);

        parse(input, 0, tokenisedSearch, 0);
    }

    public static boolean parse(String input, int searchFromIndex, List<String> tokensToSeach, int currentTokenIndex) {
        if(currentTokenIndex >= tokensToSeach.size())
            return true;
        String token = tokensToSeach.get(currentTokenIndex);
        int found = input.indexOf(token, searchFromIndex);
        if(found >= 0) {
            System.out.println("Found at Index "+found+ " Token " +token);
            return parse(input, searchFromIndex+1, tokensToSeach, currentTokenIndex+1);
        }
        return false;
    }

    public static List<String> parseExp(String exp) {
        List<String> list = new ArrayList<String>();
        String runningToken = "";
        for (int i = 0; i < exp.length(); i++) {
            char at = exp.charAt(i);
            switch (at) { 
            case '+' :
                runningToken += runningToken;
                list.add(runningToken);
                runningToken = "";
                break;
            case '-' :
                runningToken += runningToken;
                runningToken += runningToken;
                list.add(runningToken);
                runningToken = "";
                break;
            default :
                runningToken += at;
            }
        }
        return list;
    }
}

【讨论】:

    【解决方案4】:

    让我们称字符串 n 的长度和查询表达式的长度(以“单位”的数量表示,如 a+b-)为 m。

    不清楚你所说的“连续”和“非连续”是什么意思,但是如果“连续”意味着查询字符串单元之间不能有任何间隙,那么你可以使用KMP algorithm 来在 O(m+n) 时间内找到所有实例。

    我们可以用dynamic programming解决O(nm)时间和空间中的“非连续”版本。基本上,我们要计算的是一个函数:

    f(i, j) = the number of occurrences of the subquery consisting of the first i units
              of the query expression, in the first j characters of the string.
    

    因此,在您的示例中,f(2, 41) = 2,因为在示例字符串的前 41 个字符中,子模式 a+b+ 有 2 次单独出现。

    那么最终的答案就是 f(n, m)。

    我们可以递归计算如下:

    f(0, j) = 0
    f(i, 0) = 0
    f(i > 0, j > 0) = f(i, j-1) + isMatch(i, j) * f(i-1, j-len(i))
    

    其中len(i) 是表达式中第 i 个单元的长度(始终为 2 或 4),isMatch(i, j) 是一个函数,如果表达式中的第 i 个单元匹配文本结尾,则返回 1 > 在位置 j,否则为 0。例如,isMatch(15, 2) = 1 在您的示例中,因为 s[14..15] = bb。这个函数只需要固定的时间来运行,因为它永远不需要检查超过 4 个字符。

    上面的递归已经按原样工作了,但是我们可以通过确保我们只解决每个子问题一次来节省时间。因为函数 f() 仅依赖于它的 2 个参数 i 和 j,它们的范围分别在 0 和 m 之间,以及 0 和 n 之间,我们可以只计算所有 n*m 个可能的答案并将它们存储在一个表中。

    [编辑:正如 Sasha Salauyou 指出的那样,空间需求实际上可以减少到 O(m)。我们永远不需要在 k m % 2 来在它们之间交替。]强>

    【讨论】:

    • @SashaSalauyou:好点。我已经编辑解释了如何做到这一点。
    • 请查看我的非递归解决方案:stackoverflow.com/a/29953082/3459206
    • @SashaSalauyou:请在您发布之前为我发布的非递归解决方案投票 :)
    【解决方案5】:

    想自己尝试一下,然后我想我也可以分享我的解决方案。当表达式中确实存在char 0 时,parse 方法显然存在问题(尽管这本身可能是更大的问题),find 方法将因空的needles 数组而失败,而我没有确定 ab+c- 是否应该被视为有效模式(我将其视为这样)。请注意,到目前为止,这仅涵盖非连续部分。

    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.List;
    
    public class Matcher {
    
      public static void main(String[] args) {
        String haystack = "aksdbaalaskdhfbblajdfhacccc aoudgalsaa bblisdfhcccc";
        String[] needles = parse("a+b+c-");
        System.out.println("Needles: " + Arrays.toString(needles));
        System.out.println("Found: " + find(haystack, needles, 0));
        needles = parse("ab+c-");
        System.out.println("Needles: " + Arrays.toString(needles));
        System.out.println("Found: " + find(haystack, needles, 0));
      }
    
      private static int find(String haystack, String[] needles, int i) {
        String currentNeedle = needles[i];
        int pos = haystack.indexOf(currentNeedle);
        if (pos < 0) {
          // Abort: Current needle not found
          return 0;
        }
        // Current needle found (also means that pos + currentNeedle.length() will always
        // be <= haystack.length()
        String remainingHaystack = haystack.substring(pos + currentNeedle.length());
        // Last needle?
        if (i == needles.length - 1) {
          // +1: We found one match for all needles
          // Try to find more matches of current needle in remaining haystack
          return 1 + find(remainingHaystack, needles, i);
        }
        // Try to find more matches of current needle in remaining haystack
        // Try to find next needle in remaining haystack
        return find(remainingHaystack, needles, i) + find(remainingHaystack, needles, i + 1);
      }
    
      private static String[] parse(String expression) {
        List<String> searchTokens = new ArrayList<String>();
        char lastChar = 0;
        for (int i = 0; i < expression.length(); i++) {
          char c = expression.charAt(i);
          char[] chars;
          switch (c) {
            case '+':
              // last char is repeated 2 times
              chars = new char[2];
              Arrays.fill(chars, lastChar);
              searchTokens.add(String.valueOf(chars));
              lastChar = 0;
              break;
            case '-':
              // last char is repeated 4 times
              chars = new char[4];
              Arrays.fill(chars, lastChar);
              searchTokens.add(String.valueOf(chars));
              lastChar = 0;
              break;
            default:
              if (lastChar != 0) {
                searchTokens.add(String.valueOf(lastChar));
              }
              lastChar = c;
          }
        }
        return searchTokens.toArray(new String[searchTokens.size()]);
      }
    }
    

    输出:

    Needles: [aa, bb, cccc]
    Found: 4
    Needles: [a, bb, cccc]
    Found: 18
    

    【讨论】:

      【解决方案6】:

      需要O(m)空间并在O(n*m)中运行的非递归算法,其中m是查询中的标记数:

      @Test
      public void subequences() {
      
          String input = "aabbccaacccccbbd";
          String query = "a+b+";
      
          // here to store tokens of a query: e.g. {a, +}, {b, +}
          char[][] q = new char[query.length() / 2][];
      
          // here to store counts of subsequences ending by j-th token found so far
          int[] c =  new int[query.length() / 2];   // main
          int[] cc = new int[query.length() / 2];   // aux        
      
          // tokenize
          for (int i = 0; i < query.length(); i += 2)
              q[i / 2] = new char[] {query.charAt(i), query.charAt(i + 1)};
      
          // init
          char[] sub2 = {0, 0};        // accumulator capturing last 2 chars
          char[] sub4 = {0, 0, 0, 0};  // accumulator capturing last 4 chars
      
          // main loop
          for (int i = 0; i < input.length(); i++) {
      
              shift(sub2, input.charAt(i));
              shift(sub4, input.charAt(i));
      
              boolean all2 = sub2[1] != 0 && sub2[0] == sub2[1];  // true if all sub2 chars are same
              boolean all4 = sub4[3] != 0 && sub4[0] == sub4[1]   // true if all sub4 chars are same
                    && sub4[0] == sub4[2] && sub4[0] == sub4[3];
      
              // iterate tokens
              for (int j = 0; j < c.length; j++) {
      
                  if (all2 && q[j][1] == '+' && q[j][0] == sub2[0]) // found match for "+" token
                      cc[j] = j == 0             // filling up aux array
                            ? c[j] + 1           // first token, increment counter by 1
                            : c[j] + c[j - 1];   // add value of preceding token counter
      
                  if (all4 && q[j][1] == '-' && q[j][0] == sub4[0]) // found match for "-" token
                      cc[j] = j == 0 
                            ? c[j] + 1 
                            : c[j] + c[j - 1];
              }
              if (all2) sub2[1] = 0;  // clear, to make "aa" occur in "aaaa" 2, not 3 times
              if (all4) sub4[3] = 0;
              copy(cc, c);            // copy aux array to main 
              }
          }
          System.out.println(c[c.length - 1]);
      }
      
      
      // shifts array 1 char left and puts c at the end
      void shift(char[] cc, char c) {
          for (int i = 1; i < cc.length; i++)
              cc[i - 1] = cc[i];
          cc[cc.length - 1] = c;
      }
      
      // copies array contents 
      void copy(int[] from, int[] to) {
          for (int i = 0; i < from.length; i++)
              to[i] = from[i];
      }
      

      主要思想是从输入中逐个捕获字符,将它们保存在 2 字符和 4 字符的累加器中,并检查它们是否与查询的某些标记匹配,记住我们为 sub- 获得了多少匹配项到目前为止以这些标记结尾的查询。

      查询 (a+b+c-) 被拆分为标记 (a+b+c-)。然后我们在累加器中收集字符并检查它们是否与某些令牌匹配。如果我们找到第一个令牌的匹配项,我们将其计数器加 1。如果我们找到另一个 第 j 个令牌的匹配项,我们可以创建与 由令牌组成的子查询一样多的附加子序列 [0 ...j],因为它们中的许多现在存在 用于由标记 [0...j-1] 组成的子查询,因为这个匹配可以附加到它们中的每一个。

      例如,我们有:

      a+ : 3  (3 matches for a+)
      b+ : 2  (2 matches for a+b+)
      c- : 1  (1 match for a+b+c-) 
      

      cccc 到达时。那么c-计数器应该增加b+计数器值,因为到目前为止我们有2个a+b+子序列并且cccc可以附加到它们。

      【讨论】:

      • 我觉得不错! +1。实际上,我仍然会将 aa 视为在 aaaa 中出现 3 次,但问题并不清楚首选什么。
      • 太棒了。字符串 aa 的 exp a+a+ 可能需要小修复。
      【解决方案7】:

      如果您首先使用简单的解析器/编译器转换搜索字符串,使a+ 变为aa 等,那么您可以简单地获取此字符串并针对您的干草堆运行正则表达式匹配。 (抱歉,我不是 Java 编码员,所以无法提供任何真正的代码,但这并不难)

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2020-02-21
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-04-02
        相关资源
        最近更新 更多