leetcode 10. 正则表达式匹配 Regular Expression Matching
https://leetcode-cn.com/problems/regular-expression-matching/
题目描述
给定一个字符串 (s) 和一个字符模式 §。实现支持 ‘.’ 和 ‘*’ 的正则表达式匹配。
‘.’ 匹配任意单个字符。
‘*’ 匹配零个或多个前面的元素。
匹配应该覆盖整个字符串 (s) ,而不是部分字符串。
说明:
s 可能为空,且只包含从 a-z 的小写字母。
p 可能为空,且只包含从 a-z 的小写字母,以及字符 . 和 *。
示例 1:
输入:
s = “aa”
p = “a”
输出: false
解释: “a” 无法匹配 “aa” 整个字符串。
示例 2:
输入:
s = “aa”
p = “a*”
输出: true
解释: ‘*’ 代表可匹配零个或多个前面的元素, 即可以匹配 ‘a’ 。因此, 重复 ‘a’ 一次, 字符串可变为 “aa”。
示例 3:
输入:
s = “ab”
p = “."
输出: true
解释: ".” 表示可匹配零个或多个(’*’)任意字符(’.’)。
示例 4:
输入:
s = “aab”
p = “cab”
输出: true
解释: ‘c’ 可以不被重复, ‘a’ 可以被重复一次。因此可以匹配字符串 “aab”。
示例 5:
输入:
s = “mississippi”
p = “misisp*.”
输出: false
解题思路
原始思路及遇到的问题
我第一个想到的是有限自动状态机
和编译原理。
比如当前状态为1,输入字符a进入下一个状态2,如下图左;如果检测到是a*
就可以表示为下图右。(表示空输入)
比如模式为a*b*c*a
,得到的状态机如下图:
检测aaa
是否符合这个正则,以这样的路径是可以走到终点的。
但是在检验过程中存在一个问题,对于一个状态,输入同一个字符,可能有多条可选的下一条路径。
比如在状态1,输入a,可能到达2,也可能到达3,4,5.
如果检测每一条路径,那就太麻烦了。
解决办法
后来我了解到上面的自动机叫做不确定有限自动状态机(NFA,Nondeterminate Finite Automation)
,还有一种是确定有限自动状态机(DFA,Determinate Finite Automation)
。DFA的状态对于一个输入,是只有一个输出的。而且NFA是可以转化为DFA的。
涉及到的知识
有限自动状态机可以用五元组表示 {状态集合,输入集合,转移矩阵,开始状态集,结束状态集}
,用数学表示就是:NFA
NFA的构造
构造五元组,输入集合就是{a-z}
,只有一个开始状态。
状态用一个类表示:
//NFA不确定有限自动状态机的状态
class NFAState {
boolean end; // 是否是结束状态
Map<Character, Set<NFAState>> map; // 输入char,进入下一个状态
}
依次检查正则表达式中的字符,递归构造出NFA。
/**
* 构造非确定有限自动状态机
* @param pattern
* @param p 指示pattern中的位置,即pattern[p]是下一个输入
* @param state 当前节点,根据输入,构造下一个节点,递归调用
*/
private void buildNFA(String pattern, int p, NFAState state)
NFA转化为DFA
我找到的方法是子集构造法
。(并且有堆DFA进行化简的方法,我没有学,自然也没有实现)
参考:
http://www.pchou.info/resource/2013/12/31/compiler.html
https://www.bilibili.com/video/av31939713
基本思想就是合并几个状态为一个新的状态,比如当前状态为{1},输入a,可以到达的状态为2,3,4,用集合表示{2,3,4}
,新状态中还要包含空输入能到达的状态,如4输入空可以到达5,那新的状态就是{2,3,4,5}
;然后再看新的状态的转移函数,直到没有新的状态产生为止。
下面有几个比较拗口但其实意思很简单的定义:
- 状态集的闭包:状态集中的任何状态经任意条弧而能达到的所有状态的集合。表示为:
- 状态集的弧转换:状态集中的任何状态经过一条弧而能到达的所有状态的集合。表示为:
- 状态集的弧转换的闭包:
用数学语言描述NFA转换为DFA的过程是:
输入集合是相同的,其余都不相同。
开始状态:,
假如当前状态为,输入为,那下一个状态为
递归找出所有的状态和状态转换表。
如果中包含中的值,那就是DFA的结束状态。
下面是一个例子:
java源码
package algorithm;
import java.util.*;
public class FSM {
//NFA不确定有限自动状态机的状态
class NFAState {
boolean end;
Map<Character, Set<NFAState>> map; // 输入char,进入下一个状态
}
// 确定有限自动状态机的状态
class DFAState {
boolean end;
Map<Character, DFAState> map;
Set<NFAState> set; // 由NFA--》DFA的过程中,一个DFA的节点是哪些NFA的节点的集合
@Override
public boolean equals(Object object) {
DFAState dfaState = (DFAState) object;
return dfaState.set.equals(set);
}
@Override
public int hashCode() {
return set.hashCode();
}
}
/**
* 构造非确定有限自动状态机
* @param pattern
* @param p
* @param state 当前节点,根据输入,构造下一个节点,递归调用
*/
private void buildNFA(String pattern, int p, NFAState state) {
int n = pattern.length();
if (p == n) {
state.end = true;
return;
}
Character c = pattern.charAt(p);
if ((p+1) < n && pattern.charAt(p+1) == '*') {
NFAState next = new NFAState();
Map<Character, Set<NFAState>> nextMap = new HashMap<Character, Set<NFAState>>();
Set<NFAState> nextSet = new HashSet<NFAState>();
nextSet.add(next);
nextMap.put(c, nextSet);
next.map = nextMap;
f(state, c, next);
f(state, ' ', next);
buildNFA(pattern, p+2, next);
} else {
NFAState next = new NFAState();
f(state, c, next);
buildNFA(pattern, p+1, next);
}
}
/**
* 状态转移函数,状态state,输入c,转移到下一个状态next
* @param state
* @param c
* @param next
*/
private void f(NFAState state, Character c, NFAState next) {
Map<Character, Set<NFAState>> map = state.map;
if (map == null) {
map = new HashMap<Character, Set<NFAState>>();
state.map = map;
}
if (map.containsKey(c)) {
map.get(c).add(next);
} else {
Set<NFAState> set = new HashSet<NFAState>();
set.add(next);
map.put(c, set);
}
}
private DFAState buildDFA(String pattern) {
NFAState nfa = new NFAState();
buildNFA(pattern, 0, nfa); // 不确定的有限自动状态机NFA
Set<NFAState> nfaStartSet = new HashSet<NFAState>(); // NFA的开始节点集合
nfaStartSet.add(nfa);
Set<NFAState> startClosureSet = new HashSet<NFAState>(); // NFA开始节点的epsilon闭包
epsilonClosure(nfaStartSet, startClosureSet);
startClosureSet.add(nfa);
DFAState dfaState = new DFAState(); // 确定有限自动状态机DFA
dfaState.set = startClosureSet;
if (isEnd(startClosureSet)) {
dfaState.end = true;
}
Set<DFAState> dfaStates = new HashSet<DFAState>(); // DFA所有的节点
dfaStates.add(dfaState);
allDFAs.put(dfaState.hashCode(), dfaState);
nfa2dfa(dfaState);
return dfaState;
}
/**
* 递归构建DFA
* @param state 当前节点
*/
private void nfa2dfa(DFAState state) {
Set<Character> characters = alphabet(state);
Map<Character, DFAState> map = state.map;
if (map == null) {
map = new HashMap<Character, DFAState>();
state.map = map;
}
for (Character c : characters) {
if (c != ' ') {
DFAState next = next(state, c);
if (allDFAs.values().contains(next)) {
next = allDFAs.get(next.hashCode());
}
map.put(c, next);
if (!allDFAs.values().contains(next)) {
allDFAs.put(next.hashCode(), next);
nfa2dfa(next);
}
}
}
}
Set<Character> alphabets = new HashSet<Character>();
Map<Integer, DFAState> allDFAs = new HashMap<Integer, DFAState>();
/**
* 这个状态下,经由哪些字母可以转移到下一个状态
* @param state
* @return
*/
private Set<Character> alphabet(DFAState state) {
Set<NFAState> states = state.set;
Set<Character> characters = new HashSet<Character>();
for (NFAState nfaState: states) {
if (nfaState.map != null) {
characters.addAll(nfaState.map.keySet());
}
}
if (characters.contains('.')) {
return alphabets;
}
return characters;
}
private DFAState next(DFAState state, Character c) {
DFAState next = move(state.set, c);
Set<NFAState> nextSet = new HashSet<NFAState>();
nextSet.addAll(next.set);
epsilonClosure(next.set, nextSet);
next.set = nextSet;
if (isEnd(nextSet)) {
next.end = true;
}
return next;
}
/**
* 状态集的c弧转换
* @param set
* @param c
* @return
*/
private DFAState move(Set<NFAState> set, Character c) {
DFAState dfaState = new DFAState();
Set<NFAState> nextSet = new HashSet<NFAState>();
dfaState.set = nextSet;
for (NFAState nfaState: set) {
if (nfaState.map != null) {
if (nfaState.map.get(c) != null) {
nextSet.addAll(nfaState.map.get(c));
}
if (nfaState.map.get('.') != null) {
nextSet.addAll(nfaState.map.get('.'));
}
}
}
return dfaState;
}
/**
* 状态集list的epsilon-闭包
* @param set
* @param closureSet 代表闭包,返回值,应该传入初始化过的集合
*/
private void epsilonClosure(Set<NFAState> set, Set<NFAState> closureSet) {
for (NFAState nfaState: set) {
if (nfaState.map != null) {
Set<NFAState> nextSet = nfaState.map.get(' ');
if (nextSet != null) {
closureSet.addAll(nextSet);
epsilonClosure(nextSet, closureSet);
}
}
}
}
private boolean isEnd(Set<NFAState> set) {
for(NFAState nfaState: set) {
if (nfaState.end) return true;
}
return false;
}
public boolean isMatch(String s, String p) {
for (char i = 'a'; i < 'z'; i++) {
alphabets.add(i);
}
DFAState state = buildDFA(p);
int i = 0;
for (; i < s.length(); i++) {
char c = s.charAt(i);
Map<Character, DFAState> map = state.map;
if (map == null) break;
if (map.containsKey(c)) {
state = map.get(c);
} else if (map.containsKey('.')) {
state = map.get('.');
} else {
break;
}
}
return state.end && (i==s.length());
}
public static void main(String[] args) {
FSM fsm = new FSM();
String s = "aab", p = "c*a*b";
boolean b = fsm.isMatch(s, p);
System.out.println(b);
}
}