44. Wildcard Matching - cocoder39/coco39_LC GitHub Wiki

44. Wildcard Matching

Notes 2020:

similar to 10. Regular Expression Matching

Option 1: dp

class Solution:
    def isMatch(self, s: str, p: str) -> bool:
        dp = [[False] * (len(p)+1) for i in range(len(s)+1)]
        dp[-1][-1] = True
        for i in range(len(s), -1, -1):
            for j in range(len(p)-1, -1, -1):
                if p[j] == '*':
                    dp[i][j] = dp[i][j+1] or (i < len(s) and dp[i+1][j])
                else:
                    dp[i][j] = i < len(s) and p[j] in {'?', s[i]} and dp[i+1][j+1]
        return dp[0][0]

Option 2: recursion + memo

(sCur < len(s) and helper(sCur+1, pCur))

only if s[sCur] is a valid char then we can do helper(sCur+1, pCur)). That's why we need to check sCur < len(s)

  • time complexity: O(s * p) as we need to fill each entry in memo
  • space complexity: O(s * p) for memo and O(s+p) for call stack
class Solution:
    def isMatch(self, s: str, p: str) -> bool:
        
        def helper(sCur, pCur):
            if sCur in memo and pCur in memo[sCur]:
                return memo[sCur][pCur]
            
            if pCur == len(p):
                return sCur == len(s)
            
            res = False
            if p[pCur] == '*':
                res = helper(sCur, pCur+1) or (sCur < len(s) and helper(sCur+1, pCur))
            else:
                res = sCur < len(s) and p[pCur] in {'?', s[sCur]} and helper(sCur+1, pCur+1)
            
            memo[sCur][pCur] = res
            return res
            
        memo = collections.defaultdict(dict)
        return helper(0, 0)

Option 3: iteration

time complexity is O(s * p). EG, for example below, '*' will match 0 char, then 1 char, then 2 chars ...

s = aaaaaaa....b
p = *aaaaab
class Solution:
    def isMatch(self, s: str, p: str) -> bool:
        s_len, p_len = len(s), len(p)
        s_idx, p_idx = 0, 0
        star_idx, s_tmp_idx = -1, -1
        
        while s_idx < s_len:
            if p_idx < p_len and p[p_idx] in {'?', s[s_idx]}:
                s_idx += 1
                p_idx += 1
            elif p_idx < p_len and p[p_idx] == '*':
                # optimistic match: assuming there is a match without using '*'
                star_idx = p_idx
                s_tmp_idx = s_idx
                p_idx += 1
            elif star_idx == -1: # mismatch + no start was found
                return False
            else:
                p_idx = star_idx + 1
                s_idx = s_tmp_idx + 1
                s_tmp_idx = s_idx
        
        for i in range(p_idx, p_len):
            if p[i] != '*':
                return False
        return True
                
        

========================================================================

time is O(m * n) and memory is O(m * n)

bool isMatch(string s, string p) {
        int m = s.length();
        int n = p.length();
        vector<vector<bool>> dp(m + 1, vector<bool>(n + 1));
        
        dp[0][0] = true;
        for (int i = 1; i <= m; i++) {
            dp[i][0] = false;
        }
        for (int i = 1; i <= n; i++) {
            dp[0][i] = dp[0][i - 1] && p[i -1] == '*'; 
        }
        
        for (int i = 1; i <= m; i++) {
            for (int j = 1; j <= n; j++) {
                if (p[j - 1] != '*') {
                    dp[i][j] = dp[i - 1][j - 1] && (p[j - 1] == s[i - 1] || p[j - 1] == '?'); 
                }
                else { //          empty    ||      at least one char
                    dp[i][j] = dp[i][j - 1] || dp[i - 1][j];
                }
            }
        }
        return dp[m][n];
    }

memory can be optimized to O(n)

dp[cur][0] = false works as

for (int i = 1; i <= m; i++) {
            dp[i][0] = false;
        }

in above code

bool isMatch(string s, string p) {
        int m = s.length();
        int n = p.length();
        //dp[pre][j] = true if s[0 .. i] matches p[0 .. j]
        //dp[cur][j] = true if s[0 .. i + 1] matches p[0 .. j]
        vector<vector<bool>> dp(2, vector<bool>(n + 1));
        
        dp[0][0] = true;
        //dp[1][0] = false;
        for (int j = 1; j <= n; j++) {
            dp[0][j] = dp[0][j - 1] && p[j -1] == '*'; 
        }
        
        int pre = 0, cur = 1;
        for (int i = 1; i <= m; i++) {
            dp[cur][0] = false; //bug if miss this line
            for (int j = 1; j <= n; j++) {
                if (p[j - 1] != '*') {
                    dp[cur][j] = dp[pre][j - 1] && (p[j - 1] == s[i - 1] || p[j - 1] == '?'); 
                }
                else { //          empty    ||      at least one char
                    dp[cur][j] = dp[cur][j - 1] || dp[pre][j];
                }
            }
            swap(cur, pre);
        }
        return dp[pre][n];
    }

second round: how to use '' case 1: '' matches empty => dp[i][j] = dp[i][j - 1] case 2: '' matches one or more sequence => * matches ab works as a* matches ab where '' matches empty or sequence after 'a' (exactly definition of '*') => dp[i][j] = dp[i - 1][j]

bool isMatch(string s, string p) {
        int m = s.length(), n = p.length();
        vector<vector<bool>> dp(m + 1, vector<bool>(n + 1)); //dp[i][j] = true if p[0..j-1] matches s[0..i-1]
        /*
        for (int i = 0; i <= m; i++) {
            if (i != 0) {
                dp[i][0] = false;
            } else {
                dp[0][0] = true;
            }
        }
        */
        dp[0][0] = true;
        for (int i = 1; i <= n; i++) {
            dp[0][i] = dp[0][i - 1] && p[i - 1] == '*';
        }
        
        for (int i = 1; i <= m; i++) {
            for (int j = 1; j <= n; j++) {
                if (p[j - 1] != '*') {
                    dp[i][j] = dp[i - 1][j - 1] && (p[j - 1] == s[i - 1] || p[j - 1] == '?');
                } else {
                    dp[i][j] = dp[i][j - 1] || dp[i - 1][j];
                }
            }
        }
        return dp[m][n];
    }

optimize memory with rolling array. take care dp[i][0]

bool isMatch(string s, string p) {
        int m = s.length(), n = p.length();
        vector<vector<bool>> dp(2, vector<bool>(n + 1)); //dp[i][j] = true if p[0..j-1] matches s[0..i-1]

        dp[0][0] = true;
        for (int i = 1; i <= n; i++) {
            dp[0][i] = dp[0][i - 1] && p[i - 1] == '*';
        }
        
        int pre = 0, cur = 1;
        for (int i = 1; i <= m; i++) {
            if (i > 1) {
                dp[pre][0] = false;
            }
            dp[cur][0] = false;
            for (int j = 1; j <= n; j++) {
                if (p[j - 1] != '*') {
                    dp[cur][j] = dp[pre][j - 1] && (p[j - 1] == s[i - 1] || p[j - 1] == '?');
                } else {
                    dp[cur][j] = dp[cur][j - 1] || dp[pre][j];
                }
            }
            swap(pre, cur);
        }
        return dp[pre][n];
    }

recursion: time complexity is too high to pass

"aaabbbaabaaaaababaabaaabbabbbbbbbbaabababbabbbaaaaba"
"a*******b"
bool isMatch(string s, string p) {
        if (p.empty()) {
            return s.empty();
        } else if (p[0] == '*') {
            return isMatch(s, p.substr(1)) || (! s.empty() && isMatch(s.substr(1), p));
        } else {
            return ! s.empty() && (p[0] == s[0] || p[0] == '?') && isMatch(s.substr(1), p.substr(1));
        }
    }

iteration: kinda greedy. match as much as you can, once you can not match, go back and use '*' to match the unmatched char. time is O(m * n)

bool isMatch(string s, string p) {
        int unmatchs, star = -1;
        int i = 0, j = 0;
        while (i < s.length()) {
            if (p[j] == s[i] || p[j] == '?') {
                i++; j++; continue;
            } else if (p[j] == '*') {
                unmatchs = i; star = j++; continue;
            } else if (star >= 0) { //have met '*' before
                i = ++unmatchs; j = star + 1; continue; //use '*' to match unmatchs, ++unmatchs for repeated '*'
            } else { //i doesn't reach the end but there is unmatch
                return false;
            }
        }
        
        while (j < p.length() && p[j] == '*')   j++;
        return j == p.length();
    }
⚠️ **GitHub.com Fallback** ⚠️