簡體   English   中英

查找字符串中連續和非連續表達式的次數

[英]Finding the Number of Times an Expression Occurs in a String Continuously and Non Continuously

我通過電話進行了編碼采訪,並被問到這個問題:

給定一個String(例如):

“aksdbaalaskdhfbblajdfhacccc aoudgalsaa bblisdfhcccc”

和表達式(例如):

“A + B + C-”

哪里:

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

- :表示在重復4次之前的char

查找給定表達式出現在字符串中的次數,其中操作數不連續且連續地發生。

上面的表達式發生了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");
    }
}

在花了很多時間迭代地做了之后,他提到了遞歸,我仍然看不到一個明確的方式遞歸地做,我無法解決問題。 我現在試圖在面試后解決它,但我仍然不確定如何解決這個問題。 我該如何解決這個問題? 解決方案明顯嗎? 我認為對於編碼電話采訪來說這是一個非常難的問題。

非遞歸算法,需要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字符累加器中並檢查它們是否與查詢的某些標記匹配,記住我們獲得的子查詢結束的子匹配數量到目前為止這些令牌。

查詢( 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可以附加到它們兩者。

讓我們調用字符串n的長度和查詢表達式的長度(以“單位”的數量表示,如a+b- )m。

目前尚不清楚“連續”和“非連續”是什么意思,但如果“連續”意味着查詢字符串單元之間不存在任何差距,那么您可以使用KMP算法查找所有實例O(m + n)時間。

我們可以通過動態編程在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個字符中有2個單獨出現的子模式a+b+

最后的答案將是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個單位與結束於位置j的文本匹配,則返回1,否則為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 <j-1來訪問f(i,k)的值,因此我們可以只存儲2,而不是在表中存儲m列,並通過始終訪問列m % 2在它們之間交替。

想親自嘗試一下,並想我也可以分享我的解決方案。 當表達式中確實存在char 0時, parse方法顯然存在問題(雖然這可能是更大的問題本身),但對於空needles陣列, find方法將失敗並且我不確定是否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

遞歸可能是以下(偽代碼):

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

將此應用於整個字符串,您將獲得非連續出現的數量。 要獲得連續出現,您根本不需要遞歸 - 只需將表達式轉換為字符串並通過迭代進行搜索。

如何預處理aksdbaalaskdhfbblajdfhacccc aoudgalsaa bblisdfhcccc?

這變成a1k1s1d1b1a2l1a1s1k1d1h1f1b2l1a1j1d1f1h1a1c4a1o1u1d1g1a1l1s1a2b2l1i1s1d1f1h1c4

現在找到a2,b2,c4的出現次數。

嘗試下面的代碼,但現在它首先只提供基於深度的第一個可能的匹配。

需要改變以完成所有可能的組合,而不僅僅是第一次

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

如果您首先使用簡單的解析器/編譯器轉換搜索字符串,以便a+變為aa等,那么您可以簡單地使用此字符串並針對您的干草堆運行正則表達式匹配。 (對不起,我不是Java編碼器,因此不能提供任何真正的代碼,但這並不困難)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM