乘風破浪:LeetCode真題_010_Regular Expression Matching

乘風破浪:LeetCode真題_010_Regular Expression Matching

1、前言

    關於正則表達式咱們使用得很是多,可是若是讓咱們本身寫一個,倒是有很是大的困難的,咱們可能想到狀態機,肯定,非肯定狀態機確實是一種解決方法,不過須要消耗很大的時間去推理和計算,對於正則表達式的縮小版,咱們每每能夠經過遞歸,遞推,動態規劃等方法來解決。java

2、Regular Expression Matching

2.1 問題理解

2.2 問題分析和解決

    遇到這樣的問題,咱們想到了遞歸,對於.是很好處理和匹配的,可是若是和*結合起來就變化多端了,正是由於*咱們纔要遞歸。正則表達式

    讓咱們看看官方的答案:算法

class Solution {
    public boolean isMatch(String text, String pattern) {
        if (pattern.isEmpty()) return text.isEmpty();
        boolean first_match = (!text.isEmpty() &&
                               (pattern.charAt(0) == text.charAt(0) || pattern.charAt(0) == '.'));

        if (pattern.length() >= 2 && pattern.charAt(1) == '*'){
            return (isMatch(text, pattern.substring(2)) ||
                    (first_match && isMatch(text.substring(1), pattern)));
        } else {
            return first_match && isMatch(text.substring(1), pattern.substring(1));
        }
    }
}

    若是模式串和源串第一個字符可以正常匹配,而且不爲空,模式串的第二個字符不爲'*',那麼咱們能夠繼續遞歸匹配下面的東西:express

1 return first_match && isMatch(text.substring(1), pattern.substring(1));

    若是模式串的長度大於1,而且第二個字符是*,那麼咱們就有可能匹配到源串的不少的字符,也就至關於將源串已經匹配的去掉,拿剩下的和整個模式串繼續比較,此時*發揮了做用,或者比較源串與去掉了*的模式串,由於*沒有可以發揮做用。因而就獲得了:spa

1         if (pattern.length() >= 2 && pattern.charAt(1) == '*'){
2             return (isMatch(text, pattern.substring(2)) ||
3                     (first_match && isMatch(text.substring(1), pattern)));
4         } 

     除此以外咱們還可使用動態規劃算法:3d

class Solution {
    public boolean isMatch(String text, String pattern) {
        boolean[][] dp = new boolean[text.length() + 1][pattern.length() + 1];
        dp[text.length()][pattern.length()] = true;

        for (int i = text.length(); i >= 0; i--){
            for (int j = pattern.length() - 1; j >= 0; j--){
                boolean first_match = (i < text.length() &&
                                       (pattern.charAt(j) == text.charAt(i) ||
                                        pattern.charAt(j) == '.'));
                if (j + 1 < pattern.length() && pattern.charAt(j+1) == '*'){
                    dp[i][j] = dp[i][j+2] || first_match && dp[i+1][j];
                } else {
                    dp[i][j] = first_match && dp[i+1][j+1];
                }
            }
        }
        return dp[0][0];
    }
}

     首先咱們定義dp[i][j]表明源串T[i:]和模式串P[j:]是匹配的,其中i,j爲源串和模式串的下標,因而咱們只要求得dp[0][0]的值就能夠了。咱們已知的條件是:code

 dp[text.length()][pattern.length()] = true;

     因而咱們從後往前倒求最終的dp[0][0],經過以下的判斷,看看是哪種狀況,而後根據相應的狀況採起不一樣的遞推策略,最終獲得結果:blog

1    boolean first_match = (i < text.length() &&
2                             (pattern.charAt(j) == text.charAt(i) ||
3                             pattern.charAt(j) == '.'));
4    if (j + 1 < pattern.length() && pattern.charAt(j+1) == '*'){
5          dp[i][j] = dp[i][j+2] || first_match && dp[i+1][j];
6    } else {
7          dp[i][j] = first_match && dp[i+1][j+1];
8    }

       一樣的咱們算法也是使用了遞歸和動態規劃:遞歸

    在動態規劃方面咱們使用match[i]來表示對於源串從i到最後(T[i:])都是可以匹配的,因而之用求match[0]便可。element

import java.util.Arrays;

public class Solution {
    /**
     * Implement regular expression matching with support for '.' and '*'.
     * '.' Matches any single character.
     * '*' Matches zero or more of the preceding element.
     *
     * 題目大意:
     * 實現一個正則表達式匹配算法,.匹配任意一個字符,*匹配0個或者多個前導字符
     */
    public boolean isMatch(String s, String p) {
        boolean[] match = new boolean[s.length() + 1];
        Arrays.fill(match, false);
        match[s.length()] = true;//剛開始知足須要
        for (int i = p.length() - 1; i >= 0; i--) {
            if (p.charAt(i) == '*') {
                for (int j = s.length() - 1; j >= 0; j--)  {
          //原來就是false只有可以爲真,才爲真。 match[j] = match[j] || match[j + 1] && (p.charAt(i - 1) == '.' || s.charAt(j) == p.charAt(i - 1)); } i--; } else { for (int j = 0; j < s.length(); j++) {
//從前日後,只有到了已經有true的時候才能生效。若是從後往前反而有問題。 match[j] = match[j + 1] && (p.charAt(i) == '.' || p.charAt(i) == s.charAt(j)); }           //將最後的置爲假,原本就應該不真,便於之後的判斷 match[s.length()] = false; } } return match[0]; } // 下面的代碼用時比較長 public boolean isMatch2(String s, String p) { // 輸入都爲null if (s == null && p == null) { return true; } // 有一個爲null else if (s == null || p == null) { return false; } return isMatch(s, 0, p, 0); } /** * 正則表達式匹配 * * @param s 匹配串 * @param sIdx 當前匹配的位置 * @param p 模式串 * @param pIdx 模式串的匹配位置 * @return 匹配結果 */ public boolean isMatch(String s, int sIdx, String p, int pIdx) { // 同時到各自的末尾 if (s.length() == sIdx && p.length() == pIdx) { return true; } // 當匹配串沒有到達末尾,模式串已經到了末尾 else if (s.length() != sIdx && p.length() == pIdx) { return false; } // 其它狀況 else { // 若是當前匹配的下一個字符是*號 if (pIdx < p.length() - 1 && p.charAt(pIdx + 1) == '*') { // 匹配串未結束而且當前字符匹配(字符相等或者是.號) if (sIdx < s.length() && (s.charAt(sIdx) == p.charAt(pIdx) || p.charAt(pIdx) == '.')) { return isMatch(s, sIdx + 1, p, pIdx + 2) // 匹配串向前移動一個字符(只匹配一次) || isMatch(s, sIdx + 1, p, pIdx) // 匹配串向前移動一個字符(下一次匹配一樣的(模式串不動)) || isMatch(s, sIdx, p, pIdx + 2); // 忽略匹配的模式串 } else { // 忽略* return isMatch(s, sIdx, p, pIdx + 2); } } // 匹配一個字符 if (sIdx < s.length() && (s.charAt(sIdx) == p.charAt(pIdx) || p.charAt(pIdx) == '.')) { return isMatch(s, sIdx + 1, p, pIdx + 1); } } return false; } }

 

    以下表所示,使用遞歸須要1163ms而使用動態規劃須要20ms,差異很是顯著。

3、總結

     對於一些比較困難的問題,咱們須要從不一樣的角度考慮,解決問題的方法能夠從遞歸,遞推,動態規劃等方面去考慮。

相關文章
相關標籤/搜索