--- title: 【LeetCode】0044. Wildcard Matching date: 2018-12-20 is_modified: false disqus: cynthiahackmd categories: - "面試刷題" tags: - "LeetCode" --- {%hackmd @CynthiaChuang/Github-Page-Theme %} <br> Given an input string (`s`) and a pattern (`p`), implement wildcard pattern matching with support for `'?'` and `'*'`. ``` '?' Matches any single character. '*' Matches any sequence of characters (including the empty sequence). ``` The matching should cover the **entire** input string (not partial). <!--more--> > **Note:** > - `s` could be empty and contains only lowercase letters `a-z`. > - `p` could be empty and contains only lowercase letters `a-z`, and characters like `?` or `*`. <br> **Example 1:** ```python Input: s = "aa" p = "a" Output: False Explanation: "a" does not match the entire string "aa". ``` **Example 2:** ```python Input: s = "aa" p = "*" Output: True Explanation: '*' matches any sequence. ``` **Example 3:** ```python Input: s = "cb" p = "?a" Output: False Explanation: '?' matches 'c', but the second letter is 'a', which does not match 'b'. ``` **Example 4:** ```python Input: s = "adceb" p = "*a*b" Output: True Explanation: The first '*' matches the empty sequence, while the second '*' matches the substring "dce". ``` **Example 5:** ```python Input: s = "acdcb" p = "a*c?b" Output: False ``` <br> **Related Topics:** `String`、`Backtracking`、`Dynamic Programming`、`Greedy` ## 解題邏輯與實作 有點像是第十題的 [Regular Expression Matching](/interview/problemset/2018/12/19/LeetCode-0010-Regular-Expression-Matching/),不過符號的意義不太一樣,這邊使用的 `?` 表示一個萬用字元, `*` 則是表示多個萬用字元。 ### 遞迴 跟 Regular Expression 相比最大的差異應該是 `*` 的用法。在 Regular Expression 中 `*` 不可能單獨存在,必定會跟在某個字元後面,在產生 subpattern 時要特別注意;但在這題中 `*` 可以單獨存在,subpatter 應該只有 pattern 或 pattern[1:] 兩種。 1. 若 pattern 為空字串,則檢查 string 是否為空字串 1. 是,則回傳 True 2. 否,則回傳 False 2. 檢查 string 是否為空字串 1. 是,判斷 pattern 是否為 `*` 1. 是,呼叫遞迴傳入 string 與 pattern[1:] 2. 否,則回傳 False 2. 否,向下執行 3. pattern 的字首與 string 的字首是否匹配(匹配情況包含 pattern 字首為 `?` 與 `*` 的情況) 1. 是,判斷 pattern 是否為 `*` 1. 是,則呼叫遞迴傳入分別傳入 string[1:] 與 pattern 和 string[1:] 與 pattern[1:] 2. 否,則呼叫遞迴傳入 string[1:] 與 pattern[1:] 2. 否,則回傳 False <br> 一樣多加了參數暫存比較結果,節省呼叫次數。不過按照上面那個邏輯去寫在遇到這個 [testcase](https://leetco%20de.com/submissions/detail/195948838/testcase/) 時會 Memory Limit Exceeded,所以修改了第二個判斷式的邏輯,判斷 pattern 扣除掉 `*` 長度: 1. 若 pattern 扣除掉 `*`長度為 0,則回傳 True - 代表 pattern 剩下的整句都是 `*`,因此不用管 string 內容是什麼一定會匹配。 2. 若 pattern 扣除掉 `*` 長度大於 string 的長度,則回傳 False - 也就是即便 `*` 全用來匹配空字串,仍會有部份 pattern 無法匹配上 string,因為除`*` 外的 pattern 符號都必須匹配一個字元,但目前 pattern 比 string 還長... - 另外 string 長度等於 0 的 case,也包含在這,因此原先判斷式中對於 string 長度為 0 的 case 可以全刪除了。 ```python= class Solution: def __init__(self): self.memo = {} def isMatch(self, s, p): if (s,p) in self.memo: return self.memo.get((s,p)) p_len = len(p) s_len = len(s) sub_p_len = len(p.replace("*","")) if p_len == 0: return s_len == 0 if sub_p_len == 0 : return True if sub_p_len > s_len: self.memo[(s,p)] = False return False result = False if p[0]=='?' or p[0]=='*' or p[0] == s[0]: if p[0] == '*': result = self.isMatch(s[1:] , p) or self.isMatch(s , p[1:]) result = result or self.isMatch(s[1:] , p[1:]) self.memo[(s,p)] = result return result ``` 總算可以過了,但說實話效能不是很好,跑出了 1096 ms、40.91 %的成績,還有非常大的改進空間。 ### 雙指標 原本是在找 Dynamic Programming 的解法,但連續找到兩篇說用 DP 會 TLE,說要改用雙指標回溯,所以我決定也來寫寫看: 1. 初始化兩個指標 s_index 與 p_index,分別指向 string 與 pattern 的字首 2. 判斷 s_index 是否小於 string 長度 1. 是 1. 若指標位置可以匹配(匹配情況包含 pattern 為 `?` ,但排除為 `*` 的情況),兩個指標皆向後移動 2. 若 pattern 目前指向為 `*`,則 pattern 指標向後移動,並記下目前指標位置(last_s_index 與 last_p_index) - 先假設 `*` 匹配的是空字元的情況,向後進行匹配, 3. 假設 `*` 匹配空字元狀況下,無法完成整句匹配,因此所記錄的位置,且 last_s_index 加一 - 表示它匹配了 `*`,因此可以不再考慮 4. 若上述的 Case 皆無法匹配,則為回傳 False 2. 否,向下執行 3. 在掃完 string 後,檢查 pattern 是否還有剩下除 `*` 外的字元 1. 有,表示未完成匹配,回傳 False 2. 無,則回傳 True ```python= class Solution(object): def isMatch(self, s, p): """ :type s: str :type p: str :rtype: bool """ s_len, p_len = len(s), len(p) s_idx, p_idx = 0, 0 last_s_idx, last_p_idx = -1, -1 while s_idx < s_len: if p_idx < p_len and (p[p_idx]=='?' or p[p_idx]==s[s_idx]): s_idx += 1 p_idx += 1 elif p_idx < p_len and p[p_idx]=='*' : p_idx += 1 last_s_idx, last_p_idx = s_idx , p_idx elif last_p_idx != -1 : last_s_idx += 1 s_idx, p_idx = last_s_idx, last_p_idx else: return False return len(p[p_idx:].replace("*","")) == 0 ``` 效能好上不少,136 ms、 83.93% ### Dynamic Programming & Greedy Algorithm 是說看標籤應該還可以用 Dynamic Programming 跟 Greedy Algorithm 來解題,先欠著回頭再來研究 XD ## 其他連結 1. [【LeetCode】0000. 解題目錄](/x62skqpKStKMxRepJ6iqQQ) <br><br> > **本文作者**: 辛西亞.Cynthia > **本文連結**: [辛西亞的技能樹](https://cynthiachuang.github.io/LeetCode-0044-Wildcard-Matching) / [hackmd 版本](https://hackmd.io/@CynthiaChuang/LeetCode-0044-Wildcard-Matching) > **版權聲明**: 部落格中所有文章,均採用 [姓名標示-非商業性-相同方式分享 4.0 國際](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en) (CC BY-NC-SA 4.0) 許可協議。轉載請標明作者、連結與出處!