Medium Problemsstringgrid

Regular Expression Matching

Given a text string and a pattern containing '.' for any single character and '*' meaning zero or more of the character right before it, decide whether the pattern matches the entire text.

Do this lesson first: longest common subsequence

Example input

pattern = "c*a*b.*c", text = "aabxbcyc"

Expected output

1 (the pattern matches)

Break it down

Answer each question out loud before you open it. Getting it wrong here is the useful part. A revealed answer you never guessed at teaches you nothing.

Fill the table

The table pauses before each cell you have to supply. Type the value the recurrence gives, and the write animation confirms it.

Step not started

Press start. The animation stops at every cell YOUR recurrence must fill.

Row 0 and column 0 are given rather than computed. dp[0][0] = 1: the empty pattern matches the empty text. dp[0][j] = 0 for j from 1 up: the empty pattern takes no letters. Column 0 is the pattern against the empty text, and an x* unit is the one thing that can match nothing, so dp[i][0] = 1 exactly when the first i characters cut into x* units throughout: i is even and the 2nd, 4th and every later even-position character up to i is a *. That is a closed form of the pattern alone, and the snippet writes it that way. On c*a*b.*c it is 1 at rows 2 and 4 (c* and c*a*) and 0 at rows 1, 3, 5, 6, 7 and 8: rows 1 and 3 end on a letter whose star is not in the prefix yet, and from row 5 down the bare b needs a letter. At the drive size that is 17 bases: 9 across row 0 for columns 0 to 8, and 8 more down column 0. A star cell reads two rows up and to its left in its own row; a letter or . cell reads the diagonal. A row-major sweep, left to right within each row, has written each of those before it is needed. The two-row jump has a consequence you can see in the table: rows 1, 3 and 6, the c, the a and the . that a star follows, are filled like the rest, yet no later cell reads any of them. The star row below each one jumps over it to the row above, and checks its letter against the text directly rather than through a cell. Their 1s, such as dp[3][1] (c*a matches a), are true and go unused; taking exactly one copy of x is covered by the star row instead, through its left read.

Rows ↓
Cols →
∅
1 a
2 a
3 b
4 x
5 b
6 c
7 y
8 c
∅
1
0
0
0
0
0
0
0
0
1 c
0
0
0
0
0
0
0
0
0
2 *
1
0
0
0
0
0
0
0
0
3 a
0
1
0
0
0
0
0
0
0
4 *
1
1
1
0
0
0
0
0
0
5 b
0
0
0
1
0
0
0
0
0
6 .
0
0
0
0
1
0
0
0
0
7 *
0
0
0
1
1
1
1
1
1
8 c
0
0
0
0
0
0
1
0
1

Rows ↓pattern: c*a*b.*c

Cols →text: the first n letters of aabxbcycd

regular-expression-matching.ts
  1. 1function isMatch(p, t) {
  2. 2 const m = p.length, n = t.length;
  3. 3 const fits = (x, y) => (x === "." || x === y ? 1 : 0);
  4. 4 const unitsOfStars = (i) => i % 2 === 0 && [...p.slice(0, i)].every((c, k) => k % 2 === 0 || c === "*");
  5. 5 const dp = Array.from({ length: m + 1 }, (_, i) =>
  6. 6 Array.from({ length: n + 1 }, (_, j) => (j === 0 && unitsOfStars(i) ? 1 : 0)));
  7. 7 for (let i = 1; i <= m; i++) {
  8. 8 for (let j = 1; j <= n; j++) {
  9. 9 dp[i][j] = p[i - 1] === "*"
  10. 10 ? Math.max(dp[i - 2][j], Math.min(fits(p[i - 2], t[j - 1]), dp[i][j - 1]))
  11. 11 : Math.min(fits(p[i - 1], t[j - 1]), dp[i - 1][j - 1]);
  12. 12 }
  13. 13 }
  14. 14 return dp[m][n];
  15. 15}
Base caseComputedBeing readAnswer

The code, the trap, the variations

regular-expression-matching.ts
  1. 1function isMatch(p, t) {
  2. 2 const m = p.length, n = t.length;
  3. 3 const fits = (x, y) => (x === "." || x === y ? 1 : 0);
  4. 4 const unitsOfStars = (i) => i % 2 === 0 && [...p.slice(0, i)].every((c, k) => k % 2 === 0 || c === "*");
  5. 5 const dp = Array.from({ length: m + 1 }, (_, i) =>
  6. 6 Array.from({ length: n + 1 }, (_, j) => (j === 0 && unitsOfStars(i) ? 1 : 0)));
  7. 7 for (let i = 1; i <= m; i++) {
  8. 8 for (let j = 1; j <= n; j++) {
  9. 9 dp[i][j] = p[i - 1] === "*"
  10. 10 ? Math.max(dp[i - 2][j], Math.min(fits(p[i - 2], t[j - 1]), dp[i][j - 1]))
  11. 11 : Math.min(fits(p[i - 1], t[j - 1]), dp[i - 1][j - 1]);
  12. 12 }
  13. 13 }
  14. 14 return dp[m][n];
  15. 15}

Where people go wrong

Treating this * like wildcard matching's: stopping the star reads dp[i - 1][j], one row up, and column 0 is 1 only under a run of stars. Row i - 1 ends on the starred letter x itself, so zero copies has quietly become exactly one, and with the left read adding more, every x* now means one or more copies. On this input the pattern turns into c+a+b.+c, which needs the text to open with a c; this one opens with a, so at the drive size every cell below row 0 is 0, and the slider gives 0 at all six lengths where the right answers are 0, 0, 1, 0, 1, 0. Half the mistake can hide. Keep column 0's correct closed form and change only the star's upward read, and at the drive size it changes one cell, dp[7][3] (.* taking nothing after aab), from 1 to 0, and no answer on the slider moves. Dropping a star means dropping its letter with it, which is why the read goes two rows up.

  • Add a + meaning one or more copies of the character in front of it, and use it in place of a star.

    A + row gets its own rule, and the letter, . and * rules stay. x+ is x followed by x*, and the bare x's row just above already says x was taken once, so the + row reads ONE row up for no more copies and keeps the star's left read for one more: dp[i][j] = max(dp[i - 1][j], min(fits(x, t[j - 1]), dp[i][j - 1])). That is the pitfall's formula, right this time, and it means the bare letter's row is read after all. Row 0 stays as it is; column 0 keeps its closed form with only * counted, so a prefix holding an x+ gets 0, since x+ needs a letter. Put c+ in front, c+a*b.*c, and the slider gives 0 at all six lengths, because the text opens with a, not c. Put a+ in place of a*, c*a+b.*c, and it stays 0, 0, 1, 0, 1, 0: in both matches on the slider a* takes two copies, aa, so asking for at least one changes nothing.

  • Add a ? meaning zero or one copy of the character in front of it (the regular-expression ?, not wildcard matching's any-one-letter ?), and use it in place of a star.

    A ? row gets its own rule, and the letter, . and * rules stay. It reads two rows up for both of its cases and loses the left read, because x? cannot repeat. Zero copies is dp[i - 2][j], as for a star. One copy takes t[j - 1] and spends the unit, which is the diagonal two rows up: dp[i][j] = max(dp[i - 2][j], min(fits(x, t[j - 1]), dp[i - 2][j - 1])). Row 0 stays; column 0 counts a ? like a *, since x? can match nothing, so dp[i][0] = 1 when i is even and each even-position character is a * or a ?. With c*a?b.*c the slider gives 0 at all six lengths, because a? takes at most one a and the text opens with two. With c?a*b.*c it stays 0, 0, 1, 0, 1, 0, since c* took no c's in either match.