今天我在Algorithms类中遇到了这个问题:
给定函数
maxSubstring(s, t)
,其中s
是一个字符串,t
是s
的子字符串,找到可以删除第一个或最后一个的最大迭代次数子串t
的出现。
以下是maxSubstring
和s = banababbaa
上调用的t = ba
函数的可视化。
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
要求它被调用两次(一次用于子串的第一个索引,另一次用于最后一个索引)那个子串)。
最后,你介意告诉我我的解决方案的Big O符号是什么,以及你的Big O符号是什么?这不是原始挑战的一部分,但我很好奇,我自己。提前谢谢。
答案 0 :(得分:3)
你提出的天真递归算法的主要问题是它经常在同一个输入s
上调用 - 指数通常甚至,并且正是这导致明显的减慢更大的字符串。
你可以做的就是使用memoisation - 记住查找表中特定输入的结果。
您可以做的另一项优化是检查是否删除第一个与最后一个导致完全不同的结果。在大多数情况下,删除它们的顺序绝对无关紧要,可能的删除次数总是相同的。但是,匹配的子字符串可以与自身重叠时不是这种情况。例如,请尝试maxSubstring('ababaa', 'aba')
。
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)));
}
时间复杂性分析
递归调用的数量应该是删除次数的大致二次方。根据我的第二个建议,在最好的情况下它可能会下降到线性(取决于模式)。
对于每次通话,请考虑线性搜索(indexOf
,slice
等)和Map
查询,但其平均复杂程度将低于输入获得的平均复杂程度较小,并且通常在输入的早期发现模式。无论如何,复杂性是多项式的,而不是指数的。