package com.freetymekiyan.algorithms.level.hard;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
/**
* Implement regular expression matching with support for '.' and '*'.
* <p>
* '.' Matches any single character.
* '*' Matches zero or more of the preceding element.
* <p>
* The matching should cover the entire input string (not partial).
* <p>
* The function prototype should be:
* bool isMatch(const char *s, const char *p)
* <p>
* Some examples:
* isMatch("aa","a") → false
* isMatch("aa","aa") → true
* isMatch("aaa","aa") → false
* isMatch("aa", "a*") → true
* isMatch("aa", ".*") → true
* isMatch("ab", ".*") → true
* isMatch("aab", "c*a*b") → true
* <p>
* Company Tags: Google, Uber, Airbnb, Facebook, Twitter
* Tags: Dynamic Programming, Backtracking, String
*L
*/
public class RegularExpressionMatching {
private RegularExpressionMatching r;
/**
* DP. O(mn) Time, O(mn) Space.
* f[i][j]: whether s[0..i-1] matches p[0..j-1]
* Recurrence relations:
* if p[j - 1] != '*':
* | f[i][j] = f[i - 1][j - 1] and (s[i - 1] == p[j - 1] or p[j - 1] == '.')
* if p[j - 1] == '*', denote p[j - 2] with x, x can be '.'
* | f[i][j] is true iff any of the following is true:
* | 1) "x*" repeats 0 time and matches empty: f[i][j - 2]
* | 2) "x*" repeats >= 1 times and matches "x*x": s[i - 1] == x && f[i - 1][j]
* '.' matches any single character
* Base cases:
* When s and p are both empty, match.
* When p is empty, but s is not, don't match.
* When s is empty, but p is not, only matches when the last of p is '*' and previous pattern also matches.
* That's p[j-1] == '*' && f[0][j-2]
*/
public boolean isMatch(String s, String p) {
int m = s.length(), n = p.length();
boolean[][] dp = new boolean[m + 1][n + 1];
// Base cases
dp[0][0] = true;
// for (int i = 1; i <= m; i++) { // Is false by default.
// dp[i][0] = false;
// }
for (int j = 1; j <= n; j++) {
// Note that dp[0][1] is false no matter what.
// "p[j-2]*" can only match empty, so dp[0][j] depends on dp[0][j-2].
dp[0][j] = j > 1 && dp[0][j - 2] && p.charAt(j - 1) == '*';
}
// Build matrix
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
if (p.charAt(j - 1) != '*') { // Last characters match and previous also match.
dp[i][j] = dp[i - 1][j - 1] && (s.charAt(i - 1) == p.charAt(j - 1) || '.' == p.charAt(j - 1));
} else {
dp[i][j] = dp[i][j - 2] // "p[j-2]*" repeats 0 times and matches empty.
/*
* "p[j-2]*" repeats >= 1 times to match s[i-1].
* p[j-2] must match s[i-1] and s[0..i-2] match p[0..j-1].
* e.g., s:"acc", p:"ac*", "ac" matches "ac*".
*/
|| (s.charAt(i - 1) == p.charAt(j - 2) || '.' == p.charAt(j - 2)) && dp[i - 1][j];
}
}
}
return dp[m][n];
}
@Before
public void setUp() {
r = new RegularExpressionMatching();
}
@Test
public void testExamples() {
Assert.assertFalse(r.isMatch("aa", "a"));
Assert.assertTrue(r.isMatch("aa", "aa"));
Assert.assertFalse(r.isMatch("aaa", "aa"));
Assert.assertTrue(r.isMatch("aa", "a*"));
Assert.assertTrue(r.isMatch("aa", ".*"));
Assert.assertTrue(r.isMatch("ab", ".*"));
Assert.assertTrue(r.isMatch("aab", "c*a*b"));
}
@After
public void tearDown() {
r = null;
}
}