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
 * Similar Problems:  (H) Wildcard Matching
 */
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;
    }
}

@@ -838,49 +838,9 @@
 g%0A *
- Similar Problems:  (H) Wildcard Matching
+L
 %0A */

update regular expression matching