字符串匹配问题算法总结

@author stormma
@date 2018/03/24


生命不息,奋斗不止


题目1

Implement wildcard pattern matching with support for '?' and '*'.

'?' Matches any single character.
'*' Matches any sequence of characters (including the empty sequence).

The matching should cover the entire input string (not partial).

The function prototype should be:
bool isMatch(const char *s, const char *p)

Some examples:
isMatch("aa","a") → false
isMatch("aa","aa") → true
isMatch("aaa","aa") → false
isMatch("aa", "*") → true
isMatch("aa", "a*") → true
isMatch("ab", "?*") → true
isMatch("aab", "c*a*b") → false

DP思路分析

首先我们选取状态, 用boolean[][] dp数组来标记s中i个字符串是否匹配于p中j个字符串, 当然如果匹配则为True, else False
下面我们来分析一下状态是怎么变化的。

且看图:

通过上面的状态转移图, 我们很容易可以得到:
1. 边界初始化, 当si = 0;(即s字符串取0个字符), ps 0 -> p.length()的初始化为: 如果此时的p[pi]这个字符是*的话,
那么它的值应该是上一个的值, 即dp[0][pi] = dp[0][pi - 1], 如果此时p[pi] != '*'呢, 那么肯定是False. 当pi = 0;,
初始化dp[si][0] = False && si >= 1(p中取出0个字符, s中取出任意个(>0), 都是不匹配的), dp[0][0] = True(此时s中取0个字符和p中取0个字符, 无疑是匹配的).
2. 边界初始化完成之后, 我们分析一下其他地方怎么转移. 分为以下两种情况来讨论:
- if p[pi] == '*', 无非是从它的左边和上边转移而来的。
- if p[pi] == '?' || p[pi] == s[si], 那么它肯定是从它左上角转移而来。

状态转移分析完成之后, 显然答案便是dp[s.length()][p.length()]

AC代码

/**
     * dp解决
     * time running time: O(n*m)
     * extra space: O(n*m)
     */
    static class Solution2 {
        public boolean isMatch(String s, String p) {
            if (s == null && p == null) return true;
            if (s == null || p == null) return false;
            // dp[i][j]表示s字符串中前i个和p中前j个字符是否匹配
            boolean[][] dp = new boolean[s.length() + 1][p.length() + 1];
            dp[0][0] = true;
            // 初始化边界
            for (int i = 1; i <= p.length(); i++) {
                dp[0][i] = p.charAt(i - 1) == '*' && dp[0][i - 1];
            }
            for (int i = 1; i <= s.length(); i++) {
                for (int j = 1; j <= p.length(); j++) {
                    if (p.charAt(j - 1) == '?' || s.charAt(i - 1) == p.charAt(j - 1)) {
                        dp[i][j] = dp[i - 1][j - 1];
                    } else if (p.charAt(j - 1) == '*') {
                        // ab      abcd
                        // ab*     ab*
                        dp[i][j] = dp[i][j - 1] || dp[i - 1][j];
                    }
                }
            }
            return dp[s.length()][p.length()];
        }
    }

解法2

我们可以用一个指针来记录*出现的位置, 再用两个指针标记sp的位置: sppp指针, 再定义一个
辅助变量match, 分析如下:
1. 如果此时s[sp] == p[pp] || p[pp] == '?', 那么我们只需让sppp指针走一步即可。
2. 如果此时的p[pp] == '*', 我们用star来记录一下此时*出现的位置, 用辅助变量match来记录一下
此时的s走到了哪个地方, 然后让pp再走一步。
3. 如果此时star存在有效的值, 并且都不满足以上两种情况, 那么我们让pp回到上一次出现*的下一个位置, 让p
回到上次出现*的时候, s的位置的下一个位置。

为什么这样做呢? 我们来看一个例子:

s = bbaec
p = *c
sp = 0, pp = 0, match = 0, star = pp = 0, match = sp = 0 => pp++ => pp = 1
sp = 0, pp = 1, match = 0, star = 0; 此时b != c :
    然后s继续走: pp = star + 1 = 1, match++ => match = 1, sp = 1
sp = 1, pp = 1, match = 1, star = 0, 此时 b != c:
    s继续走: pp = star + 1 = 1, match++ => match = 2, sp = 2
sp = 2, pp = 1, match = 2, star = 0, 此时 a != c:
    s继续走: pp = star + 1 = 1, match++ => match = 3, sp = 3
sp = 3, pp = 1, match = 3, star = 0, 此时 e != c:
    s继续走: pp = star + 1 = 1, match++ => match = 4, sp = 4
sp = 4, pp = 1, match = 4, star = 0, 此时 c == c:
    sp++ => sp = 5, pp++ => pp = 2
退出循环, 说明`s`走到了头, 但是不确定`p`是否走到了头, 外加一个判断p的循环即可解决。

说到底, 如果遇到不符合字符串不相等并且不存在*的时候, 就回退到上一个star出现的地方, 然后跳过可能被*
匹配的字符。

AC代码

static class Solution1 {
        /**
         * time running: O(n)
         * bbarc
         * *c
         * @param s
         * @param p
         * @return
         */
        public boolean isMatch(String s, String p) {
            if (s == null && p == null) return true;
            if (s == null || p == null) return false;
            int sp = 0, pp = 0, star = -1, match = 0;
            while (sp < s.length()) {
                if (pp < p.length() && (s.charAt(sp) == p.charAt(pp) || p.charAt(pp) == '?')) {
                    sp++;
                    pp++;
                } else if (pp < p.length() && p.charAt(pp) == '*') {
                    star = pp;
                    match = sp;
                    pp++;
                } else if (star != -1) {
                    pp = star + 1;
                    match++;
                    sp = match;
                } else {
                    return false;
                }
            }
            while (pp < p.length() && p.charAt(pp) == '*') pp++;
            return pp == p.length();
        }
    }

题目2

Implement regular expression matching with support for '.' and '*'.

'.' Matches any single character.
'*' Matches zero or more of the preceding element.

The matching should cover the entire input string (not partial).

The function prototype should be:
bool isMatch(const char *s, const char *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

DP思路分析

此题和上一个题很类似, 我们还是采用dp来解决, 状态选择情况一样, boolean [][] dp = new boolean[s.length() + 1][p.length() + 1]
状态转移也很简单, 在这就不画图分析了, 直接给出AC代码。

AC代码

static class Solution {
    public boolean isMatch(String s, String p) {
            if (p.isEmpty()) {
                return s.isEmpty();
            }
            boolean[][] dp = new boolean[s.length() + 1][p.length() + 1];
            dp[0][0] = true;
            for (int j = 1; j <= p.length(); j++) {
                if (p.charAt(j - 1) == '*') {
                    dp[0][j] = dp[0][j - 2];
                }
            }

            for (int i = 1; i <= s.length(); i++) {
                for (int j = 1; j <= p.length(); j++) {
                    if (p.charAt(j - 1) == '.' || s.charAt(i - 1) == p.charAt(j - 1)) {
                        dp[i][j] = dp[i - 1][j - 1];
                    } else if (p.charAt(j - 1) == '*') {
                        if (p.charAt(j - 2) == s.charAt(i - 1) || p.charAt(j - 2) == '.') {
                            dp[i][j] = dp[i][j - 2] || dp[i - 1][j];
                        } else {
                            dp[i][j] = dp[i][j - 2];
                        }
                    }
                }
            }
            return dp[s.length()][p.length()];
        }
}

博客来自于我的个人博客转载请注明出处

你可能感兴趣的:(ACMer之路,________动态规划)