字串匹配模式問題

dailybird發表於2018-07-03

題目大意:

有一個字串它的構成是詞 + 空格的組合,如“北京 杭州 杭州 北京”,
要求輸入一個匹配模式(簡單的以字元來寫), 比如 aabb, 來判斷該字串是否符合該模式。

例子:

pattern = “abba”, str=”北京 杭州 杭州 北京” 返回 ture
pattern = “aabb”, str=”北京 杭州 杭州 北京” 返回 false
pattern = “abc”, str=”北京 杭州 杭州 南京” 返回 false
pattern = “acac”, str=”北京 杭州 北京 廣州” 返回 false

程式語言:Java

1. 參考思路

對於 pattern,我們需要將其由字串拆分成字元陣列,該陣列中的每一個字元都需要對應 str 中的一個詞。

也就是說,pattern.lengthstr.split(" ").length 是務必要相等的。我們可以把這一前提當做演算法最開始的校驗條件,以下不再贅述。

1.1 解法一

對於 pattern 拆分而來的字元陣列,如:[a, b, b, a],和目標字串 str 按空格拆分出的單詞陣列,如:[北京, 杭州, 杭州, 北京],不難推理出,a、b 實際需要對應的單詞,是根據第一次出現 a、b 時,其所對應的單詞而定的。

那麼,我們就可以將這個問題的解法轉換為:

從左到右同時遍歷字元陣列和單詞陣列,並在該過程中:

  1. pattern 字元陣列第一次出現某一字元時,根據其在單詞陣列中的位置,記錄該字元對應的單詞;
  2. pattern 字元陣列第 N 次( N > 1 )出現某一字元時,取出該字元應該對應的單詞,並與當前字元對應在單詞陣列中的單詞對比,如果相同則繼續校驗,否則返回 false;
  3. 如果校驗至最後一個單次仍然成立,則返回 true。

那麼,又延伸出兩個問題:

  1. 如果知道字元是不是第一次出現呢?
  2. 如何記錄某一字元所對應的單詞呢?

藉助 Hash 資料結構,這兩個問題可以同時得到解決,假設我們有一個 HashMap<String, String> 型別的變數 hashMap,對於當前遍歷到的字元 c,和同位置的單詞 W:

  1. 通過 hashMap.get(c) == null 來判定字元是否為第一次出現;
  2. 如果是第一次出現,則通過 hashMap.put(c, W) 將該字元對應的單次記錄下來;
  3. 如果不是第一次出現,則通過 hashMap.get(c) 將其取出,並與 W 進行比較。

1.2 解法二

每次看到字串匹配,總是不自覺的想到正規表示式。

比如,對於題目中所說的 abba 匹配,其實就相當於正規表示式:^([^s]+)s1s([^s]+)s2$。這裡用到了正則中捕獲組和反向引用的內容,詳情可參考:正則基礎之——反向引用 – CSDN部落格

同樣的,解法思路為:

  1. 當字元第一次出現時,為正則字串增加 ([^s]+),即被捕獲組包裹的除空格以外的、長度大於 1 的字串,並記錄該字元對應的反向引用的索引,即第幾個捕獲組( 序號從 1 開始 );
  2. 當字元第二次出現時,根據 1 中記錄的捕獲組索引 n,為正則增加反向引用

當然,由於目標字串中的單詞使用了空格進行分割,每一小塊正則之間需要加上 s。此外,正則的首尾也需要補充相應符號。

2. 參考程式碼

個人覺得第二種解法比較有趣,下面貼出第二種解法的核心程式碼:

public static void main(String[] args) {
    if (args.length < 2) {
        logger.info("@@@@ 用法:<model> <target>");
        System.exit(1);
    }

    // 用 model 表示使用者指定的「模式」
    // 用 pattern 表示正則
    String modelStr = args[0];
    String target = args[1];

    logger.info("@@@@ 匹配模式為:" + modelStr + ",測試內容為:" + target);

    final String regexAnyWithoutSpace = "([^\s]+)";
    final String regexSpace = "\s";

    // key 為字元,value 為反向引用序號,用於構造正規表示式
    Map<String, Integer> regexPlaceholderIndexMap = new HashMap<>();
    // 正則中,反向引用序號從 1 開始
    Integer regexPlaceholderIndex = 1;
    // 生成正規表示式
    List<String> regexPatternArray = new ArrayList<>();

    Integer patternLength = modelStr.length();
    for (Integer index = 0; index < patternLength; index++) {
        String needle = new String(new char[]{modelStr.charAt(index)});
        Integer currentPlaceHolderIndex = regexPlaceholderIndexMap.get(needle);
        if (currentPlaceHolderIndex == null) {
            // 如果為空,證明這一字元剛剛出現
            regexPatternArray.add(regexAnyWithoutSpace);
            regexPlaceholderIndexMap.put(needle, regexPlaceholderIndex);
            regexPlaceholderIndex++;
        } else {
            // 如果不為空,則證明為先前的某一字元,需用反向引用方式指代
            regexPatternArray.add(String.format("\%d", currentPlaceHolderIndex));
        }
    }
    // 將正則各個部分用空格串起來,與需求匹配
    // 併為其增加頭尾限制
    String patternStr = String.format("^%s$", String.join(regexSpace, regexPatternArray));
    logger.info("@@@@ 組裝的正規表示式為:" + patternStr);

    Pattern pattern = Pattern.compile(patternStr);
    Boolean isMatch = pattern.matcher(target).find();

    logger.info("@@@@ 字串匹配結果為:" + isMatch);
}

程式碼 Gist 地址:字串匹配模式問題 · GitHub

參考連結

  1. 正則基礎之——反向引用 – CSDN部落格

相關文章