题目描述
给定一个字符串 s
和模板串 p
,实现支持通配符 '.'
和 '*'
的正则表达式匹配。
'.'
: 可以匹配任意单个字符。'*'
: 表示零个或任意多个前一个字符。
所谓匹配,是要涵盖 整个 字符串 s
的,而不是部分字符串。
样例
输入:s = "aa", p = "a"
输出:false
解释:"a" 无法匹配 "aa" 整个字符串。
输入:s = "aa", p = "a*"
输出:true
解释:因为 '*' 代表可以匹配零个或多个前面的那一个元素, 在这里前面的元素就是 'a'。
因此,字符串 "aa" 可被视为 'a' 重复了一次。
输入:s = "ab", p = ".*"
输出:true
解释:".*" 表示可匹配零个或多个('*')任意字符('.')。
限制
1 <= s.length <= 20
1 <= p.length <= 30
s
只包含从a-z
的小写字母。p
只包含从a-z
的小写字母,以及字符.
和*
。- 保证每次出现字符
*
时,前面都匹配到有效的字符。
算法
(动态规划) $O(nm)$
- 设状态 $f(i, j)$ 表示字符串
s
的前 $i$ 个字符和字符串p
的前 $j$ 个字符能否匹配。这里假设s
和p
的下标均从 1 开始。初始时,$f(0, 0) = true$。 - 平凡转移 $f(i, j) = f(i, j)$ or $f(i - 1, j - 1)$,当 $i > 0$ 且
s(i) == p(j) || p(j) == '.'
。 - 当
p(j) == '*'
时,若 $j >= 2$,$f(i, j)$ 可以从 $f(i, j - 2)$ 转移,表示丢弃这一次的'*'
和它之前的那个字符;若 $i > 0$ 且s(i) == p(j - 1)
,表示这个字符可以利用这个'*'
,则可以从 $f(i - 1, j)$ 转移,表示利用'*'
。 - 初始状态 $f(0, 0)=true$;循环枚举 $i$ 从 0 到 $n$;$j$ 从 1 到 $m$。因为 $f(0, j)$ 有可能是有意义的,需要被转移更新。
- 最终答案为 $f(n, m)$。
时间复杂度
- 状态数为 $O(nm)$,每次转移仅需常数时间,故总时间复杂度为 $O(nm)$。
空间复杂度
- 状态数为 $O(nm)$,则需要 $O(nm)$ 的数组存储状态。
C++ 代码
class Solution {
public:
bool isMatch(string s, string p) {
int n = s.length(), m = p.length();
vector<vector<bool>> f(n + 1, vector<bool>(m + 1, false));
s = " " + s;
p = " " + p;
f[0][0] = true;
for (int i = 0; i <= n; i++)
for (int j = 1; j <= m; j++) {
if (i > 0 && (s[i] == p[j] || p[j] == '.'))
f[i][j] = f[i][j] | f[i - 1][j - 1];
if (p[j] == '*') {
if (j >= 2)
f[i][j] = f[i][j] | f[i][j - 2];
if (i > 0 && (s[i] == p[j - 1] || p[j - 1] == '.'))
f[i][j] = f[i][j] | f[i - 1][j];
}
}
return f[n][m];
}
};
y总在剑指offer的三十题写的
f[i][j]
表示p从j开始到结尾,是否能匹配s从i开始到结尾与这里的设状态f(i,j)f(i,j)
表示字符串s
的前i
个字符和字符串p
的前j
个字符能否匹配表达的冲突吗?不冲突的 两种都可以 看个人 我觉得这种顺着的思路更好理解
if (j >= 2)
f[i][j] = f[i][j] | f[i][j - 2];可以说一下这种情况是怎么理解的嘛?
丢弃
*
和*
之前的字符,因为*
可以表示 0 个。请问这里为什么要
不就行了吗
这里为了转移格式统一
请问 s = ” ” + s;
p = ” ” + p;
这两句的意义是?
让字符串的有效下标从 1 开始,0 下标作为缓冲
首先感谢您的回复 , 其次我想问的问题是为啥需要这种缓冲 , 我也刷过不少别的动态规划问题(leetcode上) , 貌似没有类似的操作 , 总结下来我的问题是 : 这道题是为什么需要前面加一个空格?或者说 , 这道题的特殊性在哪 , 让我们不得不(做最好)加一个”缓冲”
下标从 1 开始边界容易处理,否则需要一堆 if else
我看您的代码涉及到了p[j - 1] , 所以p+” “我可以理解 , 但是s似乎没有必要加上缓冲吧?请问我这个对s的理解哪里有错呢?谢谢您的指点
s[i]
里会用呀,i
是从 0 开始循环的tql
请问一下大佬是按什么顺序刷题的?
我是竞赛退役选手,所以一般只刷 hard 和打周赛 hh~
厉害啊,喜欢你写的题解👍期待持续更新哦
f[i][j] = f[i][j]这个语句有什么作用?
f[i][j] = f[i][j] | f[i - 1][j - 1];
是将f[i][j] or f[i - 1][j - 1]
的结果赋值给f[i][j]
。为什么要把自己的值赋值给自己?
没有赋值给自己,是两个数求
or
之后赋值,和f[i][j] = max(f[i][j], f [i - 1][j - 1]);
或者f[i][j] = f[i][j] + f[i - 1][j - 1]
类似但f[i][j]不是默认是false吗?为什么不直接写成f[i][j] = f[i - 1][j - 1]?
在第一个
if
里是可以的,但在第二个if
里就不可以了,这里为了统一就都那么写了第二个if还是可以的- -第三个if就不可以了 233
为啥第三个就不行了啊
因为第三个情况只是使用当前通配符的情况,在第三种情况更新之前,可能前两种情况已经把
f[i][j]
更新为true
了。如果不加|
的话,相当于把之前的两种情况直接丢掉了,此时f[i-1][j] == 0
的话,那么即使之前的两种情况已经把当前状态更新为true
,这里也会强行改成f[i-1][j]
的状态。所以应该从第二种情况开始就需要加上|
以考虑之前所有情况。问下,循环枚举 i 从 0 到 nn;j 从 1 到 mm
为啥i 要从0开始? 为哈不能从1开始?
因为
i == 0
且j > 0
的状态是有意义而且需要被更新的。是特指f[j] =’*’ 的时候f[i][j]有可能为 true吗? 因为其他情况下f[i][j]为仍为false和初始化的一样
是可能的,比如
p
是c*
的时候,f[0][2]
就是true
。不过现在的写法有越界的情况,我得改下题解
老哥,为啥=“*”的时候直接从j跑到j-2了啊,这个边界如何确定的
因为不用
’*‘
会直接导致 $j$ 减少两个字符,所以要从 $j - 2$ 转移。不必考虑f[i-1][j-2]。f[i - 1][j ]应该包含了f[i-1][j-2]。
即
f[i][j] = f[i][j] | f[i - 1][j];
有道理,确实可以省略掉
f[i - 1][j - 2]
,因为到了最后,只用一次的情况会从f[i][j] = f[i][j] | f[i][j - 2]
转移。