用JavaScript优化递归字符串操作函数 问题

用JavaScript优化递归字符串操作函数 问题,javascript,string,algorithm,optimization,Javascript,String,Algorithm,Optimization,今天我在算法课上遇到了这个问题: 给定函数maxSubstring(s,t),其中s是一个字符串,t是s的子字符串,找出可以删除第一个或最后一个子字符串t的最大迭代次数 概念 下面是在s=banababaa和t=ba上调用的函数maxSubstring的可视化 b a n a b b a a 1st move: n a b a b b a or b a n a b a b a 2nd move: n a b b

今天我在算法课上遇到了这个问题:

给定函数
maxSubstring(s,t)
,其中
s
是一个字符串,
t
s
的子字符串,找出可以删除第一个或最后一个子字符串
t
的最大迭代次数

概念 下面是在
s=banababaa
t=ba
上调用的函数
maxSubstring
的可视化

          b  a  n  a  b  b  a  a
1st move: n  a  b  a  b  b  a            or   b  a  n  a  b  a  b  a
2nd move: n a b b a a  or  n a b a b a        n a b a b a  or  b a n a b a
3rd move:    n a b a   or   n a b a             n a b a    or    n a b a
4th move:             n  a                                n  a
因此,此操作需要四个步骤

企图 这是我解决这个问题的办法。它可以工作,但当我使用较大的字符串作为参数时,速度非常慢

尝试#1

function maxSubstring(s, t) {
    if (s.includes(t)) {
        var idxSubstr = s.replace(t, '');
        var lastIdxSubstr = s.substr(0, s.lastIndexOf(t)) + s.substr(s.lastIndexOf(t) + t.length, s.length);
        return 1 + Math.max(maxSubstring(idxSubstr, t), maxSubstring(lastIdxSubstr, t)));
    }
    return 0;
}
尝试#2

function maxSubstring(s, t) {
    if (s.includes(t)) {
        var idx = s.indexOf(t), lastIdx = s.lastIndexOf(t);
        var idxSubstr = s.substr(0, idx) + s.substr(idx + t.length, s.length);
        var lastIdxSubstr = s.substr(0, lastIdx) + s.substr(lastIdx + t.length, s.length);
        if (idx != lastIdx) {
            return 1 + Math.max(maxSubstring(idxSubstr, t), maxSubstring(lastIdxSubstr, t));
        } else {
            return 1 + maxSubstring(idxSubstr, t);
        }
    }
    return 0;
}
更新原因:通过将
indexOf
lastIndexOf
的值存储在变量中,效率略有变化

尝试#3

function maxSubstring(s, t) {
    var idx = s.indexOf(t);
    if (idx >= 0) {
        var lastIdx = s.lastIndexOf(t);
        var idxSubstr = s.substr(0, idx) + s.substr(idx + t.length);
        if (idx != lastIdx) {
            var lastIdxSubstr = s.substr(0, lastIdx) + s.substr(lastIdx + t.length);
            return 1 + Math.max(maxSubstring(idxSubstr, t), maxSubstring(lastIdxSubstr, t));
        } else {
            return 1 + maxSubstring(idxSubstr, t);
        }
    }
    return 0;
}
更新原因:减少了某些值被重新定义的实例,并在检查第一个索引之前阻止了
lastIndexOf
计算

回答要求 有没有什么算法或方法可以用来优化这段代码
Math.max
是罪魁祸首,因此如果有人知道如何避免完全使用此方法,我将不胜感激

换句话说,
maxSubstring
只应在其内部调用一次,但
Math.max
要求调用它两次(一次用于子字符串的第一个索引,另一次用于该子字符串的最后一个索引)


最后,你介意告诉我我的解决方案的大O符号是什么,你的解决方案的大O符号是什么吗?这不是最初挑战的一部分,但我自己也很好奇。提前感谢。

您所介绍的朴素递归算法的主要问题是,它经常在同一个输入
s
上被调用-通常是指数级偶数,这正是导致较大字符串速度明显减慢的原因

针对这种情况,您可以使用-记住查找表中特定输入的结果

您可以做的另一个优化是检查删除第一个潜在客户和最后一个潜在客户是否会产生不同的结果。在大多数情况下,以何种顺序删除它们绝对无关紧要,可能的删除次数总是相同的。但是,当匹配的子字符串可以与其自身重叠时,情况并非如此。例如,尝试
maxSubstring('abababaa','aba')

时间复杂性分析

递归调用的数量应该大致是删除数量的二次方。根据我的第二个建议,在最好的情况下(取决于模式),它可能会变成线性的


对于每个调用,线性搜索(
indexOf
slice
等)和
Map
查找中的因素,尽管它们的平均复杂度会随着输入变小而降低,并且通常在输入的早期就发现模式。在任何情况下,复杂性都是多项式,而不是指数。

如果你只担心
Math.max
,那么为什么不使用
进行比较,毕竟只有两个参数。使用三元运算符和比较运算符查找2之间的最大值。此概念在此处概述,但适用于Java。它并没有显示我的函数在运行时有很大的变化。这不会产生很大的加速,但是你的.includes()和.replace()基本上做了两次在s中查找t的相同工作,因此你可以通过组合它们来提高一点速度。事实上,您的lastIndexOf(t)调用也在执行同样昂贵的操作,因此可能只执行一次,而不是4次!嗯。其他的复杂度包括indexOf和lastIndexOf,它们最终都是循环。试试吧,谢谢你的建议!我添加了一条语句,确保只有当
indexOf
lastIndexOf
的值不相等时才调用
Math.max
。@AnthonyKrivonos我也想到了这一点,但这并没有真正的帮助——这只会发生在最后一次删除中。@AnthonyKrivonos更好的优化方法是放置
var idx=…,lastIdx=,然后测试
if(idx=-1&&lastIdx=-1)返回0;else if(idx==lastIdx)返回1;否则…
,根本不调用
包含
。@AnthonyKrivonos在您更新的尝试中(在我写这篇文章时,最多为#3),您没有包括Bergi建议的大部分性能改进,特别是记忆,如果子字符串本身没有重叠,则不分析两条路径。我怀疑添加这些增强功能可能会解决测试用例中的超时问题。@AnthonyKrivonos我不知道,递归调用的数量应该大致是删除数量的二次方。根据我的第二个建议,在最好的情况下(取决于模式),它可能会变成线性的。对于每个调用,都要考虑线性搜索(
indexOf
slice
等)和映射查找,尽管它们的平均复杂度会随着输入的变小而降低,而且模式通常在输入的早期找到。在任何情况下,复杂性都是多项式而不是指数的。
function maxSubstring(s, t, prevResults = new Map()) {
    function result(x) { prevResults.set(s, x); return x; }
    if (prevResults.has(s))
        return prevResults.get(s); // memoisation

    const first = s.indexOf(t);
    if (first == -1)
        return result(0);
    const withoutFirst = s.slice(0, first) + s.slice(first + t.length);

    const last = s.lastIndexOf(t);
    if (last == first) // only one match
        return result(1 + maxSubstring(withoutFirst, t, prevResults));

    if (t.lastIndexOf(t.charAt(t.length-1), t.length-1) == -1 // last character of t is found nowhere else in t
        || !t.includes(s.charAt(first+t.length))) // character after the match can never be part of a match
        // so this match is always removed in the optimal sequence and it doesn't matter whether as first or last
        return result(1 + maxSubstring(withoutFirst, t, prevResults));

    const withoutLast = s.slice(0, last) + s.slice(last + t.length);
    if (t.indexOf(t.charAt(0), 1) == -1 // first character of t is found nowhere else in t
        || !t.includes(s.charAt(last - 1))) // character before the match can never be part of a match
        // so this match is always removed and it doesn't matter when
        return result(1 + maxSubstring(withoutLast, t, prevResults));

    return result(1 + Math.max(maxSubstring(withoutFirst, t, prevResults),
                               maxSubstring(withoutLast, t, prevResults)));
}