Skip to content

Commit 1e7c6b1

Browse files
Create RegularExpressionMatching.java
1 parent a0b6c52 commit 1e7c6b1

File tree

1 file changed

+191
-0
lines changed

1 file changed

+191
-0
lines changed
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
package com.thealgorithms.dynamicprogramming;
2+
3+
/**
4+
* Implements regular expression matching with support for '.' and '*'.
5+
*
6+
* Problem: Given an input string s and a pattern p, implement regular expression
7+
* matching with support for '.' and '*' where:
8+
* - '.' Matches any single character
9+
* - '*' Matches zero or more of the preceding element
10+
* - The matching should cover the entire input string (not partial)
11+
*
12+
* This solution uses dynamic programming with memoization for efficient computation.
13+
*
14+
* Example:
15+
* Input: s = "aa", p = "a" → Output: false
16+
* Input: s = "aa", p = "a*" → Output: true
17+
* Input: s = "ab", p = ".*" → Output: true
18+
*
19+
* Time Complexity: O(m * n) where m is length of s and n is length of p
20+
* Space Complexity: O(m * n) for the memoization table
21+
*
22+
* @author Your Name (replace with your GitHub username)
23+
*/
24+
public final class RegularExpressionMatching {
25+
private RegularExpressionMatching() {
26+
// Private constructor to prevent instantiation
27+
}
28+
29+
/**
30+
* Determines if the input string matches the given pattern.
31+
*
32+
* @param s the input string to match (contains only lowercase English letters)
33+
* @param p the pattern (contains lowercase English letters, '.', and '*')
34+
* @return true if the entire string matches the pattern, false otherwise
35+
* @throws IllegalArgumentException if input strings are null or pattern is invalid
36+
*/
37+
public static boolean isMatch(String s, String p) {
38+
if (s == null || p == null) {
39+
throw new IllegalArgumentException("Input strings cannot be null");
40+
}
41+
42+
if (!isValidPattern(p)) {
43+
throw new IllegalArgumentException("Invalid pattern format");
44+
}
45+
46+
// Create memoization table with Boolean wrapper for null checks
47+
Boolean[][] memo = new Boolean[s.length() + 1][p.length() + 1];
48+
return dp(0, 0, s, p, memo);
49+
}
50+
51+
/**
52+
* Helper method that performs the actual dynamic programming computation.
53+
*
54+
* @param i current index in string s
55+
* @param j current index in pattern p
56+
* @param s the input string
57+
* @param p the pattern
58+
* @param memo memoization table storing computed results
59+
* @return true if s[i:] matches p[j:], false otherwise
60+
*/
61+
private static boolean dp(int i, int j, String s, String p, Boolean[][] memo) {
62+
// Return cached result if available
63+
if (memo[i][j] != null) {
64+
return memo[i][j];
65+
}
66+
67+
boolean result;
68+
69+
// Base case: pattern is exhausted
70+
if (j == p.length()) {
71+
result = (i == s.length());
72+
} else {
73+
// Check if current characters match
74+
boolean currentMatch = i < s.length() &&
75+
(p.charAt(j) == '.' || p.charAt(j) == s.charAt(i));
76+
77+
// Handle '*' operator (lookahead)
78+
if (j + 1 < p.length() && p.charAt(j + 1) == '*') {
79+
// Two possibilities:
80+
// 1. Use '*' as zero occurrences (skip current pattern character and '*')
81+
// 2. Use '*' as one or more occurrences (if current characters match)
82+
result = dp(i, j + 2, s, p, memo) ||
83+
(currentMatch && dp(i + 1, j, s, p, memo));
84+
} else {
85+
// No '*' operator, simply advance both pointers if current characters match
86+
result = currentMatch && dp(i + 1, j + 1, s, p, memo);
87+
}
88+
}
89+
90+
// Cache the result
91+
memo[i][j] = result;
92+
return result;
93+
}
94+
95+
/**
96+
* Validates that the pattern follows the constraints:
97+
* - Only contains lowercase English letters, '.', and '*'
98+
* - '*' always follows a valid character (not at start and not after another '*')
99+
*
100+
* @param p the pattern to validate
101+
* @return true if pattern is valid, false otherwise
102+
*/
103+
private static boolean isValidPattern(String p) {
104+
if (p.isEmpty()) {
105+
return true;
106+
}
107+
108+
// Check first character is not '*'
109+
if (p.charAt(0) == '*') {
110+
return false;
111+
}
112+
113+
// Check all characters are valid and '*' always follows valid character
114+
for (int i = 0; i < p.length(); i++) {
115+
char c = p.charAt(i);
116+
if (!isValidPatternChar(c)) {
117+
return false;
118+
}
119+
120+
// Check if '*' is properly positioned
121+
if (c == '*' && (i == 0 || p.charAt(i - 1) == '*')) {
122+
return false;
123+
}
124+
}
125+
126+
return true;
127+
}
128+
129+
/**
130+
* Checks if a character is valid in a pattern (lowercase letter, '.', or '*')
131+
*/
132+
private static boolean isValidPatternChar(char c) {
133+
return (c >= 'a' && c <= 'z') || c == '.' || c == '*';
134+
}
135+
136+
/**
137+
* Alternative iterative DP solution (bottom-up approach)
138+
* This version uses a 2D boolean array for tabulation.
139+
*
140+
* @param s the input string
141+
* @param p the pattern
142+
* @return true if string matches pattern, false otherwise
143+
*/
144+
public static boolean isMatchIterative(String s, String p) {
145+
if (s == null || p == null) {
146+
throw new IllegalArgumentException("Input strings cannot be null");
147+
}
148+
149+
if (!isValidPattern(p)) {
150+
throw new IllegalArgumentException("Invalid pattern format");
151+
}
152+
153+
int m = s.length();
154+
int n = p.length();
155+
156+
// dp[i][j] means s[0..i-1] matches p[0..j-1]
157+
boolean[][] dp = new boolean[m + 1][n + 1];
158+
159+
// Empty string matches empty pattern
160+
dp[0][0] = true;
161+
162+
// Handle patterns like a*, a*b*, a*b*c* that can match empty string
163+
for (int j = 2; j <= n; j++) {
164+
if (p.charAt(j - 1) == '*') {
165+
dp[0][j] = dp[0][j - 2];
166+
}
167+
}
168+
169+
// Fill the DP table
170+
for (int i = 1; i <= m; i++) {
171+
for (int j = 1; j <= n; j++) {
172+
char sc = s.charAt(i - 1);
173+
char pc = p.charAt(j - 1);
174+
175+
if (pc == '.' || pc == sc) {
176+
dp[i][j] = dp[i - 1][j - 1];
177+
} else if (pc == '*') {
178+
char prev = p.charAt(j - 2);
179+
// Zero occurrences of previous character
180+
dp[i][j] = dp[i][j - 2];
181+
// One or more occurrences if previous character matches
182+
if (prev == '.' || prev == sc) {
183+
dp[i][j] = dp[i][j] || dp[i - 1][j];
184+
}
185+
}
186+
}
187+
}
188+
189+
return dp[m][n];
190+
}
191+
}

0 commit comments

Comments
 (0)