題目大意:
有一個字串它的構成是詞 + 空格的組合,如“北京 杭州 杭州 北京”,
要求輸入一個匹配模式(簡單的以字元來寫), 比如 aabb, 來判斷該字串是否符合該模式。例子:
pattern = “abba”, str=”北京 杭州 杭州 北京” 返回 ture
pattern = “aabb”, str=”北京 杭州 杭州 北京” 返回 false
pattern = “abc”, str=”北京 杭州 杭州 南京” 返回 false
pattern = “acac”, str=”北京 杭州 北京 廣州” 返回 false程式語言:Java
1. 參考思路
對於 pattern
,我們需要將其由字串拆分成字元陣列,該陣列中的每一個字元都需要對應 str
中的一個詞。
也就是說,pattern.length
和 str.split(" ").length
是務必要相等的。我們可以把這一前提當做演算法最開始的校驗條件,以下不再贅述。
1.1 解法一
對於 pattern
拆分而來的字元陣列,如:[a, b, b, a]
,和目標字串 str
按空格拆分出的單詞陣列,如:[北京, 杭州, 杭州, 北京]
,不難推理出,a、b 實際需要對應的單詞,是根據第一次出現 a、b 時,其所對應的單詞而定的。
那麼,我們就可以將這個問題的解法轉換為:
從左到右同時遍歷字元陣列和單詞陣列,並在該過程中:
- 當
pattern
字元陣列第一次出現某一字元時,根據其在單詞陣列中的位置,記錄該字元對應的單詞; - 當
pattern
字元陣列第 N 次( N > 1 )出現某一字元時,取出該字元應該對應的單詞,並與當前字元對應在單詞陣列中的單詞對比,如果相同則繼續校驗,否則返回 false; - 如果校驗至最後一個單次仍然成立,則返回 true。
那麼,又延伸出兩個問題:
- 如果知道字元是不是第一次出現呢?
- 如何記錄某一字元所對應的單詞呢?
藉助 Hash 資料結構,這兩個問題可以同時得到解決,假設我們有一個 HashMap<String, String> 型別的變數 hashMap,對於當前遍歷到的字元 c,和同位置的單詞 W:
- 通過
hashMap.get(c) == null
來判定字元是否為第一次出現; - 如果是第一次出現,則通過
hashMap.put(c, W)
將該字元對應的單次記錄下來; - 如果不是第一次出現,則通過
hashMap.get(c)
將其取出,並與 W 進行比較。
1.2 解法二
每次看到字串匹配,總是不自覺的想到正規表示式。
比如,對於題目中所說的 abba
匹配,其實就相當於正規表示式:^([^s]+)s1s([^s]+)s2$
。這裡用到了正則中捕獲組和反向引用的內容,詳情可參考:正則基礎之——反向引用 – CSDN部落格
同樣的,解法思路為:
- 當字元第一次出現時,為正則字串增加
([^s]+)
,即被捕獲組包裹的除空格以外的、長度大於 1 的字串,並記錄該字元對應的反向引用的索引,即第幾個捕獲組( 序號從 1 開始 ); - 當字元第二次出現時,根據 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