Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions problems/0010-regular-expression-matching/analysis.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# 0010. Regular Expression Matching

[LeetCode Link](https://leetcode.com/problems/regular-expression-matching/)

Difficulty: Hard
Topics: String, Dynamic Programming, Recursion
Acceptance Rate: 30.3%

## Hints

### Hint 1

Think about breaking down the problem into smaller subproblems. When you're comparing a string with a pattern, you're essentially making decisions character by character. What information do you need to know about the rest of the string and pattern after making a decision about the current characters?

### Hint 2

The '*' character is the tricky part - it can match zero or more of the preceding character. This means when you encounter a pattern like "a*", you have two choices: either use the star (match one 'a' and keep the pattern "a*" available), or skip the star pattern entirely (match zero 'a's). Consider using dynamic programming or recursion with memoization to avoid recomputing the same subproblems.

### Hint 3

Define your state as dp[i][j], representing whether s[0...i-1] matches p[0...j-1]. The key insight is handling the star case: if p[j-1] is '*', then dp[i][j] is true if either:
1. We skip the pattern (dp[i][j-2] is true - matching zero occurrences)
2. The preceding character matches (s[i-1] matches p[j-2]) AND dp[i-1][j] is true (we consume one character from s and keep trying to match more with the same pattern)

## Approach

We'll use dynamic programming with a 2D table where dp[i][j] represents whether the first i characters of string s match the first j characters of pattern p.

**Base Cases:**
- dp[0][0] = true (empty string matches empty pattern)
- dp[0][j] can be true only if the pattern can match empty string (patterns like "a*", "a*b*", etc.)

**State Transitions:**

For each position (i, j):

1. **If p[j-1] is not '*'**:
- dp[i][j] = dp[i-1][j-1] AND (s[i-1] == p[j-1] OR p[j-1] == '.')
- This means the current characters must match, and the previous substrings must match.

2. **If p[j-1] is '*'**:
- We have two options:
- **Zero occurrences**: dp[i][j] = dp[i][j-2] (skip the character and '*')
- **One or more occurrences**: If s[i-1] matches p[j-2] (the character before '*'), then dp[i][j] = dp[i-1][j] (consume one character from s and keep the pattern)
- dp[i][j] = dp[i][j-2] OR (matches AND dp[i-1][j])
- Where matches = (s[i-1] == p[j-2] OR p[j-2] == '.')

**Example Walkthrough:**
For s = "aa" and p = "a*":
- dp[0][0] = true
- dp[0][2] = true (because "a*" can match empty string)
- dp[1][2] = true (first 'a' matches "a*")
- dp[2][2] = true (both 'a's match "a*")

## Complexity Analysis

Time Complexity: O(m * n), where m is the length of the string s and n is the length of the pattern p. We fill a 2D table of size (m+1) x (n+1).

Space Complexity: O(m * n) for the DP table. This can be optimized to O(n) by using only two rows, but the standard approach uses O(m * n).

## Edge Cases

1. **Empty string with star patterns**: Patterns like "a*b*c*" should match an empty string.
2. **Single dot**: "." should match any single character.
3. **Dot with star**: ".*" should match any string (zero or more of any character).
4. **Multiple consecutive star patterns**: "a*a*" is a valid pattern that needs proper handling.
5. **Pattern longer than string**: A pattern like "a*b*c*" can match shorter strings or even empty strings.
6. **Exact match without wildcards**: Basic string comparison when pattern has no special characters.
52 changes: 52 additions & 0 deletions problems/0010-regular-expression-matching/solution.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package main

// Dynamic Programming approach:
// dp[i][j] represents whether s[0...i-1] matches p[0...j-1]
// For each position, we check:
// 1. If current pattern char is not '*', it must match current string char (or be '.')
// 2. If current pattern char is '*', we can either skip it (zero occurrences) or use it (one or more occurrences)

func isMatch(s string, p string) bool {
m, n := len(s), len(p)

// dp[i][j] = true if s[0...i-1] matches p[0...j-1]
dp := make([][]bool, m+1)
for i := range dp {
dp[i] = make([]bool, n+1)
}

// Base case: empty string matches empty pattern
dp[0][0] = true

// Initialize first row: empty string with patterns like a*, a*b*, etc.
for j := 2; j <= n; j++ {
if p[j-1] == '*' {
dp[0][j] = dp[0][j-2]
}
}

// Fill the DP table
for i := 1; i <= m; i++ {
for j := 1; j <= n; j++ {
if p[j-1] == '*' {
// Star can match zero occurrences (skip pattern char and star)
dp[i][j] = dp[i][j-2]

// Or star can match one or more occurrences
// Check if current char matches the char before '*'
charMatch := s[i-1] == p[j-2] || p[j-2] == '.'
if charMatch {
dp[i][j] = dp[i][j] || dp[i-1][j]
}
} else {
// Current pattern char must match current string char (or be '.')
charMatch := s[i-1] == p[j-1] || p[j-1] == '.'
if charMatch {
dp[i][j] = dp[i-1][j-1]
}
}
}
}

return dp[m][n]
}
94 changes: 94 additions & 0 deletions problems/0010-regular-expression-matching/solution_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package main

import "testing"

func TestIsMatch(t *testing.T) {
tests := []struct {
name string
s string
p string
expected bool
}{
{
name: "example 1: pattern too short",
s: "aa",
p: "a",
expected: false,
},
{
name: "example 2: star matches multiple chars",
s: "aa",
p: "a*",
expected: true,
},
{
name: "example 3: dot star matches everything",
s: "ab",
p: ".*",
expected: true,
},
{
name: "edge case: empty string with star pattern",
s: "",
p: "a*",
expected: true,
},
{
name: "edge case: empty string with multiple star patterns",
s: "",
p: "a*b*c*",
expected: true,
},
{
name: "edge case: single dot matches single char",
s: "a",
p: ".",
expected: true,
},
{
name: "edge case: dot star at beginning",
s: "aab",
p: "c*a*b",
expected: true,
},
{
name: "complex pattern with multiple stars",
s: "mississippi",
p: "mis*is*p*.",
expected: false,
},
{
name: "complex pattern matching",
s: "mississippi",
p: "mis*is*ip*.",
expected: true,
},
{
name: "star with no preceding match",
s: "ab",
p: ".*c",
expected: false,
},
{
name: "exact match without wildcards",
s: "abc",
p: "abc",
expected: true,
},
{
name: "pattern longer with stars",
s: "a",
p: "ab*",
expected: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := isMatch(tt.s, tt.p)
if result != tt.expected {
t.Errorf("isMatch(%q, %q) = %v, want %v", tt.s, tt.p, result, tt.expected)
}
})
}
}