leetCode 10 Regular Expression Matching

leetcode.windliang.cc/ 第一時間發佈html

題目描述(困難難度)

一個簡單規則的匹配,「點.」表明任意字符,「星號*」 表明前一個字符重複 0 次或任意次。java

解法一 遞歸

假如沒有通配符 * ,這道題的難度就會少了不少,咱們只須要一個字符,一個字符匹配就行。若是對遞歸不是很瞭解,強烈建議看下這篇文章,能夠理清一下遞歸的思路。算法

  • 咱們假設存在這麼個函數 isMatch,它將告訴咱們 text 和 pattern 是否匹配數組

    boolean isMatch ( String text, String pattern ) ;函數

  • 遞歸規模減少優化

    text 和 pattern 匹配,等價於 text 和 patten 的第一個字符匹配而且剩下的字符也匹配,而判斷剩下的字符是否匹配,咱們就能夠調用 isMatch 函數。也就是spa

    (pattern.charAt(0) == text.charAt(0) || pattern.charAt(0) == '.')&&isMatch(text.substring(1), pattern.substring(1));
    複製代碼
  • 遞歸出口.net

    隨着規模的減少, 當 pattern 爲空時,若是 text 也爲空,就返回 True,否則的話就返回 False 。code

    if (pattern.isEmpty()) return text.isEmpty();
    複製代碼

綜上,咱們的代碼是cdn

public boolean isMatch(String text, String pattern) {
        if (pattern.isEmpty()) return text.isEmpty();
    
    	//判斷 text 是否爲空,防止越界,若是 text 爲空, 表達式直接判爲 false, text.charAt(0)就不會執行了
        boolean first_match = (!text.isEmpty() &&
                               (pattern.charAt(0) == text.charAt(0) || pattern.charAt(0) == '.'));
        return first_match && isMatch(text.substring(1), pattern.substring(1));
    }
複製代碼

當咱們考慮了 * 呢,對於遞歸規模的減少,會增長對於 * 的判斷,直接看代碼吧。

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) == '.'));
		//只有長度大於 2 的時候,才考慮 *
        if (pattern.length() >= 2 && pattern.charAt(1) == '*'){
            //兩種狀況
            //pattern 直接跳過兩個字符。表示 * 前邊的字符出現 0 次
            //pattern 不變,例如 text = aa ,pattern = a*,第一個 a 匹配,而後 text 的第二個 a 接着和 pattern 的第一個 a 進行匹配。表示 * 用前一個字符替代。
            return (isMatch(text, pattern.substring(2)) ||
                    (first_match && isMatch(text.substring(1), pattern)));
        } else {
            return first_match && isMatch(text.substring(1), pattern.substring(1));
        }
    }
複製代碼

時間複雜度:有點兒小複雜,待更。

空間複雜度:有點兒小複雜,待更。

解法二 動態規劃

上邊的遞歸,爲了方便理解,簡化下思路。

爲了判斷 text [ 0,len ] 的狀況,須要知道 text [ 1,len ]

爲了判斷 text [ 1,len ] 的狀況,須要知道 text [ 2,len ]

爲了判斷 text [ 2,len ] 的狀況,須要知道 text [ 3,len ]

...

爲了判斷 text [ len - 1,len ] 的狀況,須要知道 text [ len,len ]

text [ len,len ] 確定好求

求出 text [ len,len ] 的狀況,就知道了 text [ len - 1,len ]

求出 text [ len - 1,len ] 的狀況,就知道了 text [ len - 2,len ]

...

求出 text [ 2,len ] 的狀況,就知道了 text [1,len ]

求出 text [ l1,len ] 的狀況,就知道了 text [ 0,len ]

從而知道了 text [ 0,len ] 的狀況,求得問題的解。

上邊就是先壓棧,而後出棧,其實咱們能夠直接倒過來求,能夠省略壓棧的過程。

咱們先求 text [ len,len ] 的狀況

利用 text [ len,len ] 的狀況 ,再求 text [ len - 1,len ] 的狀況

...

利用 text [ 2,len ] 的狀況 ,再求 text [ 1,len ] 的狀況

利用 text [1,len ] 的狀況 ,再求 text [ 0,len ] 的狀況

從而求出問題的解

咱們用 $$dp[i][j]$$表示 text 從 i 開始到最後,pattern 從 j 開始到最後,此時 text 和 pattern 是否匹配。

dp[2][2]$$就是圖中橙色的部分.

```java
public boolean isMatch(String text, String pattern) {
    // 多一維的空間,由於求 dp[len - 1][j] 的時候須要知道 dp[len][j] 的狀況,
    // 多一維的話,就能夠把 對 dp[len - 1][j] 也寫進循環了
    boolean[][] dp = new boolean[text.length() + 1][pattern.length() + 1];
    // dp[len][len] 表明兩個空串是否匹配了,"" 和 "" ,固然是 true 了。
    dp[text.length()][pattern.length()] = true;

    // 從 len 開始減小
    for (int i = text.length(); i >= 0; i--) {
        for (int j = pattern.length(); j >= 0; j--) {
            // dp[text.length()][pattern.length()] 已經進行了初始化
            if(i==text.length()&&j==pattern.length()) continue;
            
            boolean first_match = (i < text.length() && j < pattern.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];
}
```

時間複雜度:假設 text 的長度是 T,pattern 的長度是 P ,空間複雜度就是 O(TP)。

空間複雜度:申請了 dp 空間,因此是 O(TP),由於每次循環咱們只須要知道 i 和 i + 1 時候的狀況,因此咱們能夠向 [第 5 題](http://leetcode.windliang.cc/leetCode-5-Longest-Palindromic-Substring.html) 同樣進行優化。

```java
	public boolean isMatch(String text, String pattern) {
		// 多一維的空間,由於求 dp[len - 1][j] 的時候須要知道 dp[len][j] 的狀況,
		// 多一維的話,就能夠把 對 dp[len - 1][j] 也寫進循環了
		boolean[][] dp = new boolean[2][pattern.length() + 1]; 
		dp[text.length()%2][pattern.length()] = true;

		// 從 len 開始減小
		for (int i = text.length(); i >= 0; i--) {
			for (int j = pattern.length(); j >= 0; j--) {
				if(i==text.length()&&j==pattern.length()) continue;
				boolean first_match = (i < text.length() && j < pattern.length()
						&& (pattern.charAt(j) == text.charAt(i) || pattern.charAt(j) == '.'));
				if (j + 1 < pattern.length() && pattern.charAt(j + 1) == '*') {
					dp[i%2][j] = dp[i%2][j + 2] || first_match && dp[(i + 1)%2][j];
				} else {
					dp[i%2][j] = first_match && dp[(i + 1)%2][j + 1];
				}
			}
		}
		return dp[0][0];
	}
```

時間複雜度:不變, O(TP)。

空間複雜度:主要用了兩個數組進行輪換,O(P)。

# 總

這道題對於遞歸的解法,感受難在怎麼去求時間複雜度,如今尚未什麼思路,之後再來補充吧。總體來講,只要理清思路,兩種算法仍是比較好理解的。
相關文章
相關標籤/搜索