字符串匹配問題算法總結

@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()];
        }
}

博客來自於我的個人博客轉載請註明出處

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章