zoukankan      html  css  js  c++  java
  • (Java) LeetCode 44. Wildcard Matching —— 通配符匹配

    Given an input string (s) and a pattern (p), 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).

    Note:

    • s could be empty and contains only lowercase letters a-z.
    • p could be empty and contains only lowercase letters a-z, and characters like ? or *.

    Example 1:

    Input:
    s = "aa"
    p = "a"
    Output: false
    Explanation: "a" does not match the entire string "aa".

    Example 2:

    Input:
    s = "aa"
    p = "*"
    Output: true
    Explanation: '*' matches any sequence.

    Example 3:

    Input:
    s = "cb"
    p = "?a"
    Output: false
    Explanation: '?' matches 'c', but the second letter is 'a', which does not match 'b'.

    Example 4:

    Input:
    s = "adceb"
    p = "*a*b"
    Output: true
    Explanation: The first '*' matches the empty sequence, while the second '*' matches the substring "dce".

    Example 5:

    Input:
    s = "acdcb"
    p = "a*c?b"
    Output: false
    

    正则匹配问题其实是一种非确定有限状态自动机的构建。本题是通配符匹配,但和正则匹配很相似。只要能构建出来这道题就解决了。尝试构建之前先看看自动机是如何模拟正则匹配的。首先要明确特殊字符'?'与'*'分别代表了什么。'?'很好理解,即它可以和任何单个非空字符匹配。而'*'可以和任何字符串(包括空串)匹配。也就是说,只要'*'出现,那么它既可以使自动机向后运行,即创造了向后的通路;也可以使自动机的下一个状态回到它所在的位置,即创造了后一个节点回到其本身的通路。因为它可以匹配任何字符串,即当需要的时候,它可以“吸收”一切使得自动机停滞不前的字符。用第四个例子举例,构建的p串自动机如下:

    这里人为添加一个实心节点作为终点。如果s全被扫描,且p最后到达了终点,即是匹配成功。模拟自动机之前,要清楚自动机总是向着匹配成功的目标运行,即要优先考虑匹配,才去向下移动二者的指针。如果不匹配,再考虑二者有没有因为'*'产生的向后或者向前的通路。而且如果s串由于不匹配而无法前进的状态只能停留一次。这里要特别解释一下这一点:如果s串遇到了一个字符,同时p串遇到了'*',尽管i此时可以向下移动(因为p对应的是'*'),也可以不移动,那么本着匹配优先向下移动的原则,i是此时应该优先停在原地,只有j可以向下移动。而如果p的下一个字符依然不能和s的字符匹配,那么p的指针j要沿着由于'*'产生的向前通路回到'*'处,而i已经上轮已经停留在此了,所以这次不能再本着匹配才能向下移动的原则留在此地了,毕竟它不是无路可走,它可以向下。如果它不想下,就会产生死循环。说的很拗口,还是举例用例子4来说,新建两个指针i和j,i和j分别指向s串与p串自动机的第一个字符,模拟如下:

    1. i -> 'a' / j -> '*' :不匹配发生,因为p串指向的字符是'*',所以s与p同时产生向下的通路,但由于这是第一次i到这个位置,本着匹配优先的原则,i留在原地,只有j后移。且同样由于此时字符是'*',产生下一个字符回溯到当前位置的通路,如图所示;

    2. i -> 'a' / j -> 'a':找到匹配,s串和p串指针同时向后移动;

    3. i -> 'd' / j -> '*':不匹配发生,因为p串指向的字符是'*',所以s与p同时产生向下的通路,但由于这是第一次i到这个位置,本着匹配优先的原则,i留在原地,只有j后移。且同样由于此时字符是'*',产生下一个字符回溯到当前位置的通路,如图所示;

    4. i -> 'd' / j -> 'b':不匹配发生,因为不匹配,且p指向的不是'*',所以i无法向后走,j亦无法向后,为了保证自动机运行,j只能沿着之前产生的通路回到之前的位置,即j回到第二个'*'处;

    5. i -> 'd' / j -> '*':不匹配发生,因为p串指向的字符是'*',所以s与p同时产生向下的通路,此时这已经是i第二次走到这个位置了,所以i必须向后运行才可以避免死循环,所以i后移。j亦后移(其实j可以不后移,但如果j停留在'*',那么下一步一定是i指向的字符又和'*'不匹配而导致j自己后移,所以此时不如直接将j后移到'*'的下一个位置,进而省略一步);

    6. i -> 'c' / j -> 'b':此时情况和第4步一样,j先向回走,i因为不是匹配留在原地一次,进而下一步的时候需要和j同时后移。所以最后的结果是和经历和第5步一样状态(i -> 'c' / j -> '*')后,i和j同时向后,即i从'c'移动到最后一个字符'b',而j从'*'向后重新移动到'b';

    7. i -> 'b' / j -> 'b':找到匹配,s串和p串指针同时向右移动。此时p已经移动到实心终点,而s亦全部扫描完成,返回匹配true。

    模拟出来自动机运行,证明了这个就是可以寻找匹配的过程,那么下面就要开始构建自动机:

    首先本着优先匹配后移的原则,如果s[i] == p[j],那么i和j同时后移。这里可以把字符'?'加进去,因为遇到'?'和遇到匹配的情况完全一样,所以s[i] == p[j] || p[j] == '?',i与j后移;

    如果不匹配,但j指向'*',那么要做三件事:1) 构建p的回路;2) 标记此刻i因为不匹配优先而停留的位置,以便i下次不会有路走而不走;3) 后移j。这时就要引入两个变量,一个来存储此时j指向的位置,用来构建回路。一个来记录i的位置,用来提醒i下次不要有路走而不走。即,pStar = j;sStar = i;j++;

    同样如果不匹配,但是i并不是无路可走(即sStar存在),那么i走到sStar的下一个位置。由于sStar存在,pStar一定存在(因为二者同时建立),那么j此刻要到pStar+1的位置(理由见步骤5的括号中解释);

    如果i与j不匹配还都无路可走,那么只能返回false,即s和p无论如何不能匹配。

    构建自动机的过程亦是完成代码的过程,详见下文代码解释。


    Java

    class Solution {
        public boolean isMatch(String s, String p) {
            char[] S = s.toCharArray(), P = p.toCharArray();
            int i = 0, j = 0, sStar = -1, pStar = -1;
            while (i < s.length()) {
                if (j < p.length() && (S[i] == P[j] || P[j] == '?')) { //如果匹配,两指针同时后移
                    i++;
                    j++;
                }
                else if (j < p.length() && P[j] == '*') { //如果不匹配但j指向'*',那么记录此时j的位置以构建回路,同时记录i的位置以标记i此时可以后移却停留在此一次,同时j后移
                    pStar = j++;
                    sStar = i;
                }
                else if (sStar >= 0) { //仍然不匹配,但是i有路可走,且i已经停在那一次了,那么i要后移,连同i停留的位置也要更新,j直接到回路'*'的后一个位置。此时j也可以取pStar,但运行速度会变慢
                    j = pStar + 1;
                    i = ++sStar;
                }
                else return false; //仍然不匹配,i与j均已无路可走,返回false
            }
            while (j < p.length() && P[j] == '*') j++; //i扫描完成后要看j能不能够到达终点,即j可以沿着'*'行程的通路一直向下
            return j == p.length(); //i与j同时到达终点完成匹配
        }
    }
  • 相关阅读:
    安装chrome driver(14)
    爬虫-selenium实现验证码自动登录(14)
    爬虫-反爬与反反爬(12)
    爬虫-模拟登录(13)
    爬虫-GIL与线程同步问题(11)
    爬虫-多进程(10)
    爬取csdn的数据与解析存储(9)
    Exchange Server 2016邮件系统建设方案
    Exchange 2016高可用及容灾架构选型参考
    Installing Exchange 2016 on Windows Server 2016 Step by Step
  • 原文地址:https://www.cnblogs.com/tengdai/p/9310900.html
Copyright © 2011-2022 走看看