10. Regular Expression Matching
Regular Expression Matching
Given an input string s
and a pattern p
, implement regular expression matching with support for '.'
and '*'
where:
'.'
Matches any single character.'*'
Matches zero or more of the preceding element.
The matching should cover the entire input string (not partial).
Example 1:
Input: s = "aa", p = "a"
Output: false
Explanation: "a" does not match the entire string "aa".
Example 2:
Input: s = "aa", p = "a*"
Output: true
Explanation: '*' means zero or more of the preceding element, 'a'. Therefore, by repeating 'a' once, it becomes "aa".
Example 3:
Input: s = "ab", p = ".*"
Output: true
Explanation: ".*" means "zero or more (*) of any character (.)".
Constraints:
1 <= s.length <= 20
1 <= p.length <= 30
s
contains only lowercase English letters.p
contains only lowercase English letters,'.'
, and'*'
.- It is guaranteed for each appearance of the character
'*'
, there will be a previous valid character to match.
Approach 1) Recursion
function isMatch(str, pat) {
return recursiveIsMatch(0, 0, str, pat);
}
function recursiveIsMatch(i, j, str, pat) {
const inputStringLength = str.length;
const patternLength = pat.length;
// Reached the end of the pattern
if (j == patternLength) {
// Return whether or not we've also reached the end of the string (entire string has passed)
return i == inputStringLength;
}
// If the current pattern character is followed by a * (is a wildcard)
if (j + 1 < patternLength && pat.charAt(j + 1) == '*') {
// Assume 0 matches of the current pattern character, move on to the next point in the pattern (after the asterisk)
if (recursiveIsMatch(i, j + 2, str, pat)) return true;
// Loop through the remaining characters, so long as they match by character (or .)
while (
i < inputStringLength &&
(pat.charAt(j) == '.' || str.charAt(i) == pat.charAt(j))
) {
// Check the rest of the string (1 character forward), against the next point in the pattern (after the asterisk)
if (recursiveIsMatch(++i, j + 2, str, pat)) return true;
}
}
// If the current pattern character is not a wildcard, and matches the current string character
else if (
i < inputStringLength &&
(pat.charAt(j) == '.' || str.charAt(i) == pat.charAt(j))
) {
// Move onto the next character, and the next character of the pattern
return recursiveIsMatch(i + 1, j + 1, str, pat);
}
// String does not match current point in pattern
return false;
}
If no paths have succeeded by the time we reach the end of the string, the pattern was not a match, and we fail out.
Approach 2) Cheating
var isMatch = function (s, p) {
const regex = new RegExp('^' + p + '$', 'm');
return regex.test(s);
};
Approach 3) Dynamic programming

- The first column — it means
p
is empty and it will match tos
only ifs
is also empty which we have stored indp[0][0]
. Thus, the remaining values of dp[0][i] will befalse
. - The first row — this is not so easy. It means which
p
matches emptys
. 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, ifs = ""
andp = "c*"
, then due to*
,c
can be replaced by 0c
s which gives us an empty string. Hence,dp[0][2] = true
. - For non-empty strings, let’s say that
s[i - 1] == p[j - 1]
this means the (i - 1)th and (j - 1)th characters are the 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 represents an empty string (0 characters), thusdp[i][j] = dp[i][j - 2]
ors[i - 1] == p[j - 2] || p[j - 2] == "."
, the then-current character of the string equals the char preceding*
in the pattern so the result isdp[i-1][j]
.
var isMatch = function (s, p) {
const rows = s.length;
const columns = p.length;
/// Base conditions
if (rows == 0 && columns == 0) {
return true;
}
if (columns == 0) {
return false;
}
// DP array
const dp = Array.from({ length: s.length + 1 }, () => [false]);
// Empty string and empty pattern are a match
dp[0][0] = true;
// Deals with patterns with *
for (let i = 1; i < columns + 1; i++) {
if (p[i - 1] === '*') {
dp[0][i] = dp[0][i - 2];
}
else {
dp[0][i] = false;
};
}
// For remaining characters
for (let i = 1; i < rows + 1; i++) {
for (let j = 1; j < columns + 1; j++) {
if (p[j - 1] === '*') {
if (p[j - 2] === s[i - 1] || p[j - 2] === '.') {
dp[i][j] = dp[i][j - 2] || dp[i - 1][j];
} else {
dp[i][j] = dp[i][j - 2];
}
} else if (p[j - 1] === s[i - 1] || p[j - 1] === '.') {
dp[i][j] = dp[i - 1][j - 1];
} else {
dp[i][j] = false;
}
}
}
return dp[rows][columns];
};
Approach 4) Backtracking / Depth-first search (DFS)
There are two normal cases without consideration of *
:
1. s === p
2. s !== p, but p === '.' and s is any character
And then let’s consider how to handle the pattern *
:
If we have a x*
in the pattern(x stands for any character), we may ignore this part of
the pattern, or delete a matching character in the text
// no match: aa , c* || match: aaa , a*
(isMatch(s, p.substring(2))
||
(first_match && isMatch(s.substring(1), p)));
According to these analyses, we can construct a depth-first search algorithm, it’s a recursive algorithm
var isMatch = function(s, p) {
let sLen = s.length;
let pLen = p.length;
if (pLen === 0) return sLen === 0;
let first_match = (sLen !== 0 && (p[0] === s[0] || p[0] === '.'));
// If a star is present in the pattern, it will be in the
// second position p[1]. Then, we may ignore this part of
// the pattern, or delete a matching character in the text.
// If we have a match on the remaining strings after any of
// these operations, then the initial inputs matched.
if (pLen >= 2 && p[1] === '*') {
// no match: aa , c* || match: aaa , a*
return (isMatch(s, p.substring(2)) || (first_match && isMatch(s.substring(1), p)));
} else {
return first_match && isMatch(s.substring(1), p.substring(1));
}
};
That’s all folks! In this post, we solved LeetCode problem 10. Regular Expression Matching
I hope you have enjoyed this post. Feel free to share your thoughts on this.
You can find the complete source code on my GitHub repository. If you like what you learn. feel free to fork 🔪 and star ⭐ it.
In this blog, I have tried to solve leetcode questions & present the most important points to consider when improving Data structure and logic, feel free to add, edit, comment, or ask. For more information please reach me here
Happy coding!
Comments
Post a Comment