Open In App

Regular Expression Matching

Last Updated : 21 Sep, 2024
Comments
Improve
Suggest changes
Like Article
Like
Report

Given a text t and a pattern p where t consists of only lowercase English alphabets while p consists of lowercase English alphabets as well as special characters ‘.’ and ‘*’, the task is to implement a function to test regular expression such that:

  • '.' Matches any single character.​​​​
  • '*' Matches zero or more of the preceding character.

Note: For each appearance of the character ‘*', there will be a previous valid character to match.

Examples:

Input: t = "aaa", p = "a"
Output: false
Explanation: "a" does not match the entire string "aaa".

Input: t = "abb", p = "a.*"
Output: true
Explanation: replace . with b then p becomes ab* now replace * with one preceeding character hence p becomes abb.

Input: t = "", p = "a*b*"
Output: true
Explanation: Note that * can match 0 occurrences also.

Naive Recursive Solution:

We can begin matching both pattern from one side. We begin from the right side. Following cases arise, when we match character by character.

Case 1 (If Last Characters are Same) : We move to the next character in both text t and pattern p

Case 2 (If Last Character of Patter is ‘.’) : We move to the next character in both text t and pattern p

Case 2 (If Last Character of Patter is ‘*’) : There must be at-least two characters in the pattern. If not, we return false. If yes, the following two cases arise.
a) The ‘*’ and character preceding it match with 0 characters of the text. We move two characters in pattern and do not move in text.
b) The ‘*’ and character preceding it match with 1 or more characters of the text. We match the preceding character of pattern with the current character of text. If match, then we move one character ahead in text and do not move in pattern.
We return false if both a) and b) are not true.

Below is implementation of the idea.

C++
#include <iostream>
using namespace std;

bool isMatchRec(string t, string p, int n, int m) {
  
    // If pattern is empty, then text must also be 
    // empty
    if (m == 0) {
        return n == 0;
    }

    // If text is empty, then pattern can have characters
    // followed by *s
    if (n == 0) {
        return (m >= 2 && p[m - 1] == '*') && 
                 isMatchRec(t, p, n, m - 2);
    }

    // If last characters of both string and pattern 
    // match, or pattern has '.'
    if (t[n - 1] == p[m - 1] || p[m - 1] == '.') {
        return isMatchRec(t, p, n - 1, m - 1);
    }

    // Handle '*' in the pattern
    if (p[m - 1] == '*' && m > 1) {
      
        // Check if '*' can represent zero occurrences 
        // of the preceding character
        bool zero = isMatchRec(t, p, n, m - 2);

        // Check if '*' can represent one or more occurrences 
        // of the preceding character
        bool oneOrMore = (p[m - 2] == t[n - 1] || p[m - 2] == '.') &&
                                       isMatchRec(t, p, n - 1, m);

        return zero || oneOrMore;
    }

    // If no match
    return false;
}

// Wrapper function
bool isMatch(string t, string p) {
    return isMatchRec(t, p, t.size(), p.size());
}

int main() {
    cout << boolalpha << isMatch("aab", "a.*") << endl; 
    cout << boolalpha << isMatch("aa", "a") << endl;    
    cout << boolalpha << isMatch("aa", "a*") << endl;   
    cout << boolalpha << isMatch("ab", ".*") << endl;  
    cout << boolalpha << isMatch("mississippi", "mis*is*p*.") << endl; 
    return 0;
}
Java
public class GfG {
    public static boolean isMatchRec(String t, String p, int n, int m) {
      
        // If pattern is empty, then text must also be
        // empty
        if (m == 0) {
            return n == 0;
        }

        // If text is empty, then pattern can have characters
        // followed by *s
        if (n == 0) {
            return (m >= 2 && p.charAt(m - 1) == '*') && 
                     isMatchRec(t, p, n, m - 2);
        }

        // If last characters of both string and pattern
        // match, or pattern has '.'
        if (t.charAt(n - 1) == p.charAt(m - 1) || p.charAt(m - 1) == '.') {
            return isMatchRec(t, p, n - 1, m - 1);
        }

        // Handle '*' in the pattern
        if (p.charAt(m - 1) == '*' && m > 1) {
          
            // Check if '*' can represent zero occurrences
            // of the preceding character
            boolean zero = isMatchRec(t, p, n, m - 2);

            // Check if '*' can represent one or more occurrences
            // of the preceding character
            boolean oneOrMore = (p.charAt(m - 2) == t.charAt(n - 1) || p.charAt(m - 2) == '.') &&
                                   isMatchRec(t, p, n - 1, m);

            return zero || oneOrMore;
        }

        // If no match
        return false;
    }

    // Wrapper function
    public static boolean isMatch(String t, String p) {
        return isMatchRec(t, p, t.length(), p.length());
    }

    public static void main(String[] args) {
        System.out.println(isMatch("aab", "a.*"));
        System.out.println(isMatch("aa", "a"));
        System.out.println(isMatch("aa", "a*"));
        System.out.println(isMatch("ab", ".*");
        System.out.println(isMatch("mississippi", "mis*is*p*."));
    }
}
Python
def is_match_rec(t, p, n, m):
  
    # If pattern is empty, then text must also be empty
    if m == 0:
        return n == 0

    # If text is empty, then pattern can have characters followed by *s
    if n == 0:
        return (m >= 2 and p[m - 1] == '*') and is_match_rec(t, p, n, m - 2)

    # If last characters of both string and pattern match, or pattern has '.'
    if t[n - 1] == p[m - 1] or p[m - 1] == '.':
        return is_match_rec(t, p, n - 1, m - 1)

    # Handle '*' in the pattern
    if p[m - 1] == '*' and m > 1:
        # Check if '*' can represent zero occurrences of the preceding character
        zero = is_match_rec(t, p, n, m - 2)

        # Check if '*' can represent one or more occurrences of the preceding character
        one_or_more = (p[m - 2] == t[n - 1] or p[m - 2] == '.') and is_match_rec(t, p, n - 1, m)

        return zero or one_or_more

    # If no match
    return False

# Wrapper function
def is_match(t, p):
    return is_match_rec(t, p, len(t), len(p))

# Example usage
print(is_match('aab', 'a.*'))
print(is_match('aa', 'a'))
print(is_match('aa', 'a*'))
print(is_match('ab', '.*'))
print(is_match('mississippi', 'mis*is*p*.'))
JavaScript
function is_match_rec(t, p, n, m) {

    // If pattern is empty, then text must also be empty
    if (m === 0) {
        return n === 0;
    }

    // If text is empty, then pattern can have characters followed by *s
    if (n === 0) {
        return (m >= 2 && p[m - 1] === '*') && is_match_rec(t, p, n, m - 2);
    }

    // If last characters of both string and pattern match, or pattern has '.'
    if (t[n - 1] === p[m - 1] || p[m - 1] === '.') {
        return is_match_rec(t, p, n - 1, m - 1);
    }

    // Handle '*' in the pattern
    if (p[m - 1] === '*' && m > 1) {
    
        // Check if '*' can represent zero occurrences of the preceding character
        const zero = is_match_rec(t, p, n, m - 2);

        // Check if '*' can represent one or more occurrences of the preceding character
        const one_or_more = (p[m - 2] === t[n - 1] || p[m - 2] === '.') && is_match_rec(t, p, n - 1, m);

        return zero || one_or_more;
    }

    // If no match
    return false;
}

// Wrapper function
function is_match(t, p) {
    return is_match_rec(t, p, t.length, p.length);
}

// Example usage
console.log(is_match('aab', 'a.*'));
console.log(is_match('aa', 'a'));
console.log(is_match('aa', 'a*'));
console.log(is_match('ab', '.*'));
console.log(is_match('mississippi', 'mis*is*p*.'));

Output
true
false
true
true
false

Dynamic Programming Solution

The above recursive solution has exponential time complexity in the worst case. Please note that we make two recursive calls in the last if condition. We can clearly notice overlapping subproblems here as we make calls for (n-1, m-1), (n, m-2) and/or (n-1, m). So we can use Dynamic Programming to solve this problem.

  • Create a boolean 2D dp array of size (n + 1) * (m + 1). Please note that the range of values in the recursion goes from 0 to text length (or n) and 0 to pattern length (or m)
  • dp[i][j] is going to be true if first i characters of text match with first j characters of pattern.
  • If both strings are empty, then it’s a match, thus, dp[0][0] = true.
  • For other cases, we simply follow the above recursive solution.
C++
#include <iostream>
#include <vector>
using namespace std;

bool isMatch(string t, string p) {
    int n = t.size();
    int m = p.size();

    // DP table where dp[i][j] means whether first i characters in t
    // match the first j characters in p
    vector<vector<bool>> dp(n + 1, vector<bool>(m + 1, false));

    // Empty pattern matches empty text
    dp[0][0] = true;

    // Deals with patterns like a*, a*b*, a*b*c* etc, where '*' 
    // can eliminate preceding character
    for (int j = 1; j <= m; ++j) {
        if (p[j - 1] == '*' && j > 1) {
            dp[0][j] = dp[0][j - 2];
        }
    }

    // Fill the table
    for (int i = 1; i <= n; ++i) {
        for (int j = 1; j <= m; ++j) {
          
            // Characters match
            if (p[j - 1] == '.' || t[i - 1] == p[j - 1]) {
                dp[i][j] = dp[i - 1][j - 1]; 
            } 
          
            else if (p[j - 1] == '*' && j > 1) {
              
                // Two cases:
                // 1. '*' represents zero occurrence of the preceding character
                // 2. '*' represents one or more occurrence of the preceding character
                dp[i][j] = dp[i][j - 2] || 
                           (dp[i - 1][j] && (p[j - 2] == t[i - 1] || p[j - 2] == '.'));
            }
        }
    }

    return dp[n][m];
}

int main() {
    cout << boolalpha << isMatch("aab", "a.*") << endl; 
    cout << boolalpha << isMatch("aa", "a") << endl; 
    cout << boolalpha << isMatch("aa", "a*") << endl; 
    cout << boolalpha << isMatch("ab", ".*") << endl;  
    cout << boolalpha << isMatch("mississippi", "mis*is*p*.") << endl; 
    return 0;
}
Java
import java.util.*;

public class GfG {
    public static boolean isMatch(String t, String p) {
        int n = t.length();
        int m = p.length();

        // DP table where dp[i][j] means whether first i characters in t
        // match the first j characters in p
        boolean[][] dp = new boolean[n + 1][m + 1];

        // Empty pattern matches empty text
        dp[0][0] = true;

        // Deals with patterns like a*, a*b*, a*b*c* etc, where '*' 
        // can eliminate the preceding character
        for (int j = 1; j <= m; ++j) {
            if (p.charAt(j - 1) == '*' && j > 1) {
                dp[0][j] = dp[0][j - 2];
            }
        }

        // Fill the table
        for (int i = 1; i <= n; ++i) {
            for (int j = 1; j <= m; ++j) {

                // Characters match
                if (p.charAt(j - 1) == '.' || t.charAt(i - 1) == p.charAt(j - 1)) {
                    dp[i][j] = dp[i - 1][j - 1];
                } 
                // Handle '*' in the pattern
                else if (p.charAt(j - 1) == '*' && j > 1) {
                  
                    // Two cases:
                    // 1. '*' represents zero occurrence of the preceding character
                    // 2. '*' represents one or more occurrence of the preceding character
                    dp[i][j] = dp[i][j - 2] || 
                               (dp[i - 1][j] && (p.charAt(j - 2) == t.charAt(i - 1) || 
                                                 p.charAt(j - 2) == '.'));
                }
            }
        }

        return dp[n][m];
    }

    public static void main(String[] args) {
        System.out.println(isMatch("aab", "a.*"));
        System.out.println(isMatch("aa", "a"));
        System.out.println(isMatch("aa", "a*"));
        System.out.println(isMatch("ab", ".*"));
        System.out.println(isMatch("mississippi", "mis*is*p*."));
    }
}
Python
def isMatch(t: str, p: str) -> bool:
    n = len(t)
    m = len(p)

    # DP table where dp[i][j] means whether first i characters in t
    # match the first j characters in p
    dp = [[False] * (m + 1) for _ in range(n + 1)]

    # Empty pattern matches empty text
    dp[0][0] = True

    # Deals with patterns like a*, a*b*, a*b*c* etc, where '*' 
    # can eliminate the preceding character
    for j in range(1, m + 1):
        if p[j - 1] == '*' and j > 1:
            dp[0][j] = dp[0][j - 2]

    # Fill the table
    for i in range(1, n + 1):
        for j in range(1, m + 1):
            
            # Characters match
            if p[j - 1] == '.' or t[i - 1] == p[j - 1]:
                dp[i][j] = dp[i - 1][j - 1]
                
            # Handle '*' in the pattern
            elif p[j - 1] == '*' and j > 1:
              
                # Two cases:
                # 1. '*' represents zero occurrence of the preceding character
                # 2. '*' represents one or more occurrence of the preceding character
                dp[i][j] = dp[i][j - 2] or (dp[i - 1][j] and (p[j - 2] == t[i - 1] or p[j - 2] == '.'))

    return dp[n][m]


if __name__ == "__main__":
    print(isMatch("aab", "a.*"))
    print(isMatch("aa", "a"))
    print(isMatch("aa", "a*"))
    print(isMatch("ab", ".*"))
    print(isMatch("mississippi", "mis*is*p*."))
JavaScript
function isMatch(t, p) {
    const n = t.length;
    const m = p.length;

    // DP table where dp[i][j] means whether first i characters in t
    // match the first j characters in p
    const dp = Array.from({ length: n + 1 }, () => Array(m + 1).fill(false));

    // Empty pattern matches empty text
    dp[0][0] = true;

    // Deals with patterns like a*, a*b*, a*b*c* etc, where '*' 
    // can eliminate the preceding character
    for (let j = 1; j <= m; ++j) {
        if (p[j - 1] === '*' && j > 1) {
            dp[0][j] = dp[0][j - 2];
        }
    }

    // Fill the table
    for (let i = 1; i <= n; ++i) {
        for (let j = 1; j <= m; ++j) {
            
            // Characters match
            if (p[j - 1] === '.' || t[i - 1] === p[j - 1]) {
                dp[i][j] = dp[i - 1][j - 1];
            } 
            
            // Handle '*' in the pattern
            else if (p[j - 1] === '*' && j > 1) {
            
                // Two cases:
                // 1. '*' represents zero occurrence of the preceding character
                // 2. '*' represents one or more occurrence of the preceding character
                dp[i][j] = dp[i][j - 2] || (dp[i - 1][j] && (p[j - 2] === t[i - 1] || p[j - 2] === '.'));
            }
        }
    }

    return dp[n][m];
}

// Testing the function
console.log(isMatch("aab", "a.*"));          // true
console.log(isMatch("aa", "a"));             // false
console.log(isMatch("aa", "a*"));            // true
console.log(isMatch("ab", ".*"));            // true
console.log(isMatch("mississippi", "mis*is*p*.")); // false

Output
true
false
true
true
false

Illustration

Let’s take an example t = "aab" and p = "c*a*b" and create a DP table.

c

*

a

*

b

0

1

2

3

4

5

0

TRUE

FALSE

TRUE

FALSE

TRUE

FALSE

a

1

FALSE

FALSE

FALSE

TRUE

TRUE

FALSE

a

2

FALSE

FALSE

FALSE

FALSE

TRUE

FALSE

b

3

FALSE

FALSE

FALSE

FALSE

FALSE

TRUE

  • First column — it means p is empty and it will match to s only if s is also empty which we have stored in dp[0][0]. Thus, remaining values of dp[0][i] will be false.
  • First row — this is not so easy. It means which p matches empty t. The answer is either an empty pattern or a pattern that represents an empty string such as "a*""x*y*""l*m*n*" and so on. In the above example, if t = "" and p = "c*", then due to *c can be replaced by 0 cs which gives us an empty string. Hence, dp[0][2] = true.
  • For non-empty strings, let’s say that t[i - 1] == p[j - 1] this means the (i – 1)th and (j – 1)th characters are same. This means, we have to check if the remaining strings are a match or not. If they are a match, then the current substrings will be a match, otherwise they won’t be a match i.e., dp[i][j] = dp[i - 1][j - 1]. We’re taking (i – 1)th and (j – 1)th characters to offset empty strings as we’re assuming our strings start from index 1.
  • If p[j - 1] == ".", then it means any single character can be matched. Therefore, here also, we will have to check if the remaining string is a match or not. Thus, dp[i][j] = dp[i - 1][j - 1].
  • If p[j - 1] == "*", then it means either it’s represents an empty string (0 characters), thus dp[i][j] = dp[i][j - 2] or t[i - 1] == p[j - 2] || p[j - 2] == ".", then current character of string equals the char preceding *' in pattern so the result is dp[i-1][j].

Time Complexity: O(m×n)
Auxiliary Space: O(m×n)



Next Article

Similar Reads