Java/kotlin 通配符匹配(算法)

這是LeetCode上面一道難度為困難的題目,和之前的正則表達(dá)式有相似的地方,最近在學(xué)習(xí)kotlin,所以附上kotlin的寫法。

44. 通配符匹配

給定一個(gè)字符串 (s) 和一個(gè)字符模式 (p) ,實(shí)現(xiàn)一個(gè)支持 '?' 和 '' 的通配符匹配。
'?' 可以匹配任何單個(gè)字符。
'
' 可以匹配任意字符串(包括空字符串)。
兩個(gè)字符串完全匹配才算匹配成功。
說(shuō)明:
s 可能為空,且只包含從 a-z 的小寫字母。
p 可能為空,且只包含從 a-z 的小寫字母,以及字符 ? 和 *。

示例 1:

輸入:
s = "aa"
p = "a"
輸出: false
解釋: "a" 無(wú)法匹配 "aa" 整個(gè)字符串。

示例 2:

輸入:
s = "aa"
p = "*"
輸出: true
解釋: '*' 可以匹配任意字符串。

示例 3:

輸入:
s = "cb"
p = "?a"
輸出: false
解釋: '?' 可以匹配 'c', 但第二個(gè) 'a' 無(wú)法匹配 'b'。

示例 4:

輸入:
s = "adceb"
p = "*a*b"
輸出: true
解釋: 第一個(gè) '*' 可以匹配空字符串, 第二個(gè) '*' 可以匹配字符串 "dce".

示例 5:

輸入:
s = "acdcb"
p = "a*c?b"
輸出: false

最基本的遞歸解法:

public boolean isMatch(String s, String p) {
        // 保存長(zhǎng)度為length()的匹配結(jié)果
        int[][] dp = new int[s.length()+1][p.length()+1];
        return isMatch(s,p,dp);
    }

    public boolean isMatch(String s,String p,int[][] dp) {
        // 如果s、p有保存的狀態(tài),直接返回
        if (dp[s.length()][p.length()]!=0) {
            return dp[s.length()][p.length()]>0;
        }
        // 基礎(chǔ)狀態(tài)1:p是空的,而s長(zhǎng)度不為0,匹配結(jié)果為false
        if (p.isEmpty() && s.length()>0) {
            return false;
        }
        // 基礎(chǔ)狀態(tài)2:s是空的,檢查p字符串是否為空或全為*字符
        // ex : s="" p="" 或 p="****"
        if (s.isEmpty()) {
            for (int i = 0; i < p.length(); i++) {
                if (p.charAt(i)!='*') {
                    return false;
                }
            }
            return true;
        }

        // 獲取首字符
        char s0 = s.charAt(0);
        char p0 = p.charAt(0);
        if (p0=='*') {
            // 如果p的首字符為*,則存在兩種情況
            // 匹配:s向右移動(dòng)一位,進(jìn)行下一個(gè)字符的匹配
            // 忽略:p向右移動(dòng)一位,進(jìn)行下一個(gè)字符的匹配
            dp[s.length()][p.length()] = isMatch(s,p.substring(1)) || isMatch(s.substring(1),p) ?1:-1;
        } else {
            // 如果p的首字符不為*,則需要滿足s0==p0 || p0 == '?'
            // 當(dāng)前字符匹配成功后,與上后續(xù)字符的匹配結(jié)果
            dp[s.length()][p.length()] = (s0==p0 || p0=='?') && isMatch(s.substring(1),p.substring(1))?1:-1;
        }
        // dp 1為匹配成功,-1為匹配失敗
        return dp[s.length()][p.length()]>0;
    }

遞歸轉(zhuǎn)動(dòng)態(tài)規(guī)劃:

    public boolean isMatchDp(String s, String p) {
        // 狀態(tài)保存
        boolean[][] dp = new boolean[s.length()+1][p.length()+1];
        for (int i = 0; i <= s.length(); i++) {
            for (int j = 0; j <= p.length(); j++) {
                // 長(zhǎng)度為i,j的匹配結(jié)果
                if (j==0) {
                    // 當(dāng)p的長(zhǎng)度為0的時(shí)候,僅有s長(zhǎng)度為0的時(shí)候匹配成功
                    dp[i][j]= i==0;
                } else {
                    // 當(dāng)p的尾字符為*的時(shí)候
                    if (p.charAt(j-1)=='*') {
                        // i=0的時(shí)候,跳過(guò)*,查看i與j-1的匹配結(jié)果
                        // ex : s = "" 長(zhǎng)度為0,p="***",dp[0][3] = dp[0][2] = dp[0][1] = dp[0][0] = true
                        dp[i][j] = i==0 || dp[i][j-1];
                        // i>0的時(shí)候,分兩種情況
                        // 匹配:s向左移動(dòng)一位
                        // 忽略:p向左移動(dòng)一位
                        dp[i][j] |= i>0 && (dp[i][j - 1] || dp[i - 1][j]);
                    } else {
                        // 尾字符不為*,此時(shí)i=0必為false
                        // i>0的時(shí)候,匹配當(dāng)前字符與s/p左移一位的狀態(tài)結(jié)果
                        dp[i][j] = i>0 && (s.charAt(i - 1) == p.charAt(j - 1) || p.charAt(j - 1) == '?') && dp[i - 1][j - 1];
                    }
                }
            }
        }
        // 返回長(zhǎng)度為length的匹配結(jié)果
        return dp[s.length()][p.length()];
    }

貪心算法:
假設(shè)1、p為“abcd”則s只需要包含abcd順序的字符即可匹配;
假設(shè)2、p為“
abcd”則s需要包含abcd子串即可匹配;
假設(shè)3、p為“ab
c”則s需要以ab開頭,c結(jié)尾;

    public boolean isMatch(String s, String p) {
        int sRight = s.length(), pRight = p.length();
        // 如果p不以*結(jié)尾,則依次匹配并將尾指針左移
        // ex : s="abcd" p="*abcd"
        // => s="" p="*"
        while (sRight > 0 && pRight > 0 && p.charAt(pRight - 1) != '*') {
            if (charMatch(s.charAt(sRight - 1), p.charAt(pRight - 1))) {
                --sRight;
                --pRight;
            } else {
                return false;
            }
        }
        // p的尾指針減到空,則s為空時(shí)匹配成功
        if (pRight == 0) {
            return sRight == 0;
        }

        int sIndex = 0, pIndex = 0;
        // 記錄匹配位置
        int sRecord = -1, pRecord = -1;

        while (sIndex < sRight && pIndex < pRight) {
            if (p.charAt(pIndex) == '*') {
                // 遇到 '*',跳過(guò)
                ++pIndex;
                // 記錄*后面的第一個(gè)位置
                sRecord = sIndex;
                pRecord = pIndex;
            } else if (charMatch(s.charAt(sIndex), p.charAt(pIndex))) {
                // 當(dāng)前字符不為*,判斷單字符是否匹配,在子串內(nèi)繼續(xù)匹配下一個(gè)字符
                ++sIndex;
                ++pIndex;
            } else if (sRecord != -1 && sRecord + 1 < sRight) {
                // 當(dāng)前字符不為*,且單字符匹配失敗,需要重新尋找子串
                // 從上一次記錄的位置后1個(gè)位置開始重新尋找
                ++sRecord;
                sIndex = sRecord;
                pIndex = pRecord;
            } else {
                return false;
            }
        }
        // 判斷pIndex到pRight是否全為*號(hào),無(wú)論s是否匹配完,p全為*號(hào)的時(shí)候都能匹配
        return allStars(p, pIndex, pRight);
    }
    // 判斷字符是否相等
    private boolean charMatch(char u, char v) {
        return u == v || v == '?';
    }
    // 判斷是否全為*
    private boolean allStars(String str, int left, int right) {
        for (int i = left; i < right; i++) {
            if (str.charAt(i) != '*') {
                return false;
            }
        }
        return true;
    }

kotlin 貪心解法:

    fun isMatch(s: String, p: String): Boolean {
        var sRight = s.length
        var pRight = p.length

        val charMatch = { u: Char, v: Char -> u == v || v == '?' }
        val allStars = { word: String, left: Int, right: Int ->
            word.subSequence(left,right).all {
                it=='*'
            }
        }

        while (sRight > 0 && pRight > 0 && p[pRight - 1] != '*') {
            if (charMatch(s[sRight - 1], p[pRight - 1])) {
                --sRight
                --pRight
            } else {
                return false
            }
        }
        if (pRight == 0) {
            return sRight == 0
        }
        var sIndex = 0
        var pIndex = 0
        var sRecord = -1
        var pRecord = -1
        while (sIndex < sRight && pIndex < pRight) {
            if (p[pIndex] == '*') {
                ++pIndex
                sRecord = sIndex
                pRecord = pIndex
            } else if (charMatch(s[sIndex], p[pIndex])) {
                ++sIndex
                ++pIndex
            } else if (sRecord != -1 && sRecord + 1 < sRight) {
                ++sRecord
                sIndex = sRecord
                pIndex = pRecord
            } else {
                return false
            }
        }
        return allStars(p, pIndex, pRight)
    }
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容