Skip to content

Commit 5b239c2

Browse files
Create RegularExpressionMatching.java
1 parent a0b6c52 commit 5b239c2

File tree

1 file changed

+179
-0
lines changed

1 file changed

+179
-0
lines changed
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
package com.thealgorithms.dynamicprogramming;
2+
3+
/**
4+
* Implements regular expression matching with support for '.' and '*'.
5+
*
6+
* <p>
7+
* The regular expression matching problem involves determining if a given string
8+
* matches a pattern containing special characters '.' and '*'. The '.' matches
9+
* any single character, while '*' matches zero or more of the preceding element.
10+
*
11+
* <p>
12+
* This solution uses dynamic programming with memoization for efficient computation.
13+
*
14+
* <p>
15+
* For more information:
16+
* @see <a href="https://en.wikipedia.org/wiki/Regular_expression">Regular Expression</a>
17+
* @see <a href="https://leetcode.com/problems/regular-expression-matching/">LeetCode Problem 10</a>
18+
*
19+
* <p>
20+
* Example:
21+
* <pre>
22+
* Input: s = "aa", p = "a" → Output: false
23+
* Input: s = "aa", p = "a*" → Output: true
24+
* Input: s = "ab", p = ".*" → Output: true
25+
* </pre>
26+
*
27+
* <p>
28+
* Time Complexity: O(m * n) where m is length of s and n is length of p
29+
* Space Complexity: O(m * n) for the memoization table
30+
*/
31+
public final class RegularExpressionMatching {
32+
private RegularExpressionMatching() {
33+
// Private constructor to prevent instantiation
34+
}
35+
36+
/**
37+
* Determines if the input string matches the given pattern.
38+
*
39+
* @param inputString the input string to match (contains only lowercase English letters)
40+
* @param pattern the pattern (contains lowercase English letters, '.', and '*')
41+
* @return true if the entire string matches the pattern, false otherwise
42+
* @throws IllegalArgumentException if input strings are null or pattern is invalid
43+
*/
44+
public static boolean isMatch(String inputString, String pattern) {
45+
if (inputString == null || pattern == null) {
46+
throw new IllegalArgumentException("Input strings cannot be null");
47+
}
48+
49+
if (!isValidPattern(pattern)) {
50+
throw new IllegalArgumentException("Invalid pattern format");
51+
}
52+
53+
Boolean[][] memo = new Boolean[inputString.length() + 1][pattern.length() + 1];
54+
return dynamicProgramming(0, 0, inputString, pattern, memo);
55+
}
56+
57+
/**
58+
* Helper method that performs the actual dynamic programming computation.
59+
*
60+
* @param stringIndex current index in string s
61+
* @param patternIndex current index in pattern p
62+
* @param inputString the input string
63+
* @param pattern the pattern
64+
* @param memo memoization table storing computed results
65+
* @return true if s[i:] matches p[j:], false otherwise
66+
*/
67+
private static boolean dynamicProgramming(int stringIndex, int patternIndex,
68+
String inputString, String pattern,
69+
Boolean[][] memo) {
70+
if (memo[stringIndex][patternIndex] != null) {
71+
return memo[stringIndex][patternIndex];
72+
}
73+
74+
boolean result;
75+
76+
if (patternIndex == pattern.length()) {
77+
result = (stringIndex == inputString.length());
78+
} else {
79+
boolean currentMatch = stringIndex < inputString.length() &&
80+
(pattern.charAt(patternIndex) == '.' ||
81+
pattern.charAt(patternIndex) == inputString.charAt(stringIndex));
82+
83+
if (patternIndex + 1 < pattern.length() && pattern.charAt(patternIndex + 1) == '*') {
84+
result = dynamicProgramming(stringIndex, patternIndex + 2, inputString, pattern, memo) ||
85+
(currentMatch && dynamicProgramming(stringIndex + 1, patternIndex, inputString, pattern, memo));
86+
} else {
87+
result = currentMatch && dynamicProgramming(stringIndex + 1, patternIndex + 1, inputString, pattern, memo);
88+
}
89+
}
90+
91+
memo[stringIndex][patternIndex] = result;
92+
return result;
93+
}
94+
95+
/**
96+
* Validates that the pattern follows the constraints.
97+
*
98+
* @param pattern the pattern to validate
99+
* @return true if pattern is valid, false otherwise
100+
*/
101+
private static boolean isValidPattern(String pattern) {
102+
if (pattern.isEmpty()) {
103+
return true;
104+
}
105+
106+
if (pattern.charAt(0) == '*') {
107+
return false;
108+
}
109+
110+
for (int i = 0; i < pattern.length(); i++) {
111+
char currentChar = pattern.charAt(i);
112+
if (!isValidPatternChar(currentChar)) {
113+
return false;
114+
}
115+
116+
if (currentChar == '*' && (i == 0 || pattern.charAt(i - 1) == '*')) {
117+
return false;
118+
}
119+
}
120+
121+
return true;
122+
}
123+
124+
/**
125+
* Checks if a character is valid in a pattern.
126+
*/
127+
private static boolean isValidPatternChar(char character) {
128+
return (character >= 'a' && character <= 'z') || character == '.' || character == '*';
129+
}
130+
131+
/**
132+
* Alternative iterative DP solution (bottom-up approach).
133+
*
134+
* @param inputString the input string
135+
* @param pattern the pattern
136+
* @return true if string matches pattern, false otherwise
137+
*/
138+
public static boolean isMatchIterative(String inputString, String pattern) {
139+
if (inputString == null || pattern == null) {
140+
throw new IllegalArgumentException("Input strings cannot be null");
141+
}
142+
143+
if (!isValidPattern(pattern)) {
144+
throw new IllegalArgumentException("Invalid pattern format");
145+
}
146+
147+
int stringLength = inputString.length();
148+
int patternLength = pattern.length();
149+
150+
boolean[][] dp = new boolean[stringLength + 1][patternLength + 1];
151+
152+
dp[0][0] = true;
153+
154+
for (int j = 2; j <= patternLength; j++) {
155+
if (pattern.charAt(j - 1) == '*') {
156+
dp[0][j] = dp[0][j - 2];
157+
}
158+
}
159+
160+
for (int i = 1; i <= stringLength; i++) {
161+
for (int j = 1; j <= patternLength; j++) {
162+
char stringChar = inputString.charAt(i - 1);
163+
char patternChar = pattern.charAt(j - 1);
164+
165+
if (patternChar == '.' || patternChar == stringChar) {
166+
dp[i][j] = dp[i - 1][j - 1];
167+
} else if (patternChar == '*') {
168+
char previousChar = pattern.charAt(j - 2);
169+
dp[i][j] = dp[i][j - 2];
170+
if (previousChar == '.' || previousChar == stringChar) {
171+
dp[i][j] = dp[i][j] || dp[i - 1][j];
172+
}
173+
}
174+
}
175+
}
176+
177+
return dp[stringLength][patternLength];
178+
}
179+
}

0 commit comments

Comments
 (0)