场景:
最近上LeetCode刷题,碰到正则表达式匹配问题,描述如下:
分析:
刷算法题本人一直遵循Kent Beck的三部曲:Make it work->Make it right->Make it fast 。
首先,随机选取一个测试用例进行匹配模拟操作,你会发现你会很自然地采用逐个字符匹配的方式进行匹配;这就意味着对于该问题的求解实际上可以划分为若干小问题的求解,那么到这就会想到可以采用递归的方式来解决该问题。
#region 解决方案一:递归
递归的基本思想在于把规模大的问题转化为规模较小的相似的子问题来解决,根据子问题规模大小又分为两种策略:线性递归(减而治之)和二分递归(分而治之);
不过多解释,上俩图来描述(有兴趣可以去搜索邓公的数据结构课程):
减治:
分治:
对于本例,采用线性递归的方式来求解:
从递归的角度看,为求解 IsMatch(s,p),需
递归求解规模为 length-1 的问题 IsMatch(s,p-1)
递归基:IsMatch(s[0],p[0])
总体思路如下:
——边界控制:考虑字符模式p和字符串s为空的情况
if (String.IsNullOrEmpty(p))
{
return String.IsNullOrEmpty(s);
}
——递归基:考虑字符模式长度为1和带有‘*’两种情形
if (p.Length == 1)
{
return s.Length == 1 && (s[0] == p[0] || p[0] == '.');
}
if (p[1] != '*')
{
if (String.IsNullOrEmpty(s))
{
return false;
}
return (s[0] == p[0] || p[0] == '.') && IsMatch(s.Substring(1), p.Substring(1));
}
while (!String.IsNullOrEmpty(s) && (s[0] == p[0] || p[0] == '.'))
{
if (IsMatch(s, p.Substring(2)))
{
return true;
}
s = s.Substring(1);
}
——递归关系
return IsMatch(s, p.Substring(2));
#endregion 递归
总结:
通过递归的方式往往只能make work和right,但是要想高效,对于规模较大的问题递归往往不适用。比如,对于常见的斐波那契数列问题,使用直接递归方式只能快速计算40+层,越往后时间消耗越长,效率非常低。直接递归可能出现大量的重复子问题(斐波那契数列计算低效的原因),存在大量重复的计算,严重影响性能。