我想写一个函数,它会找到第一次出现的两个相同的相邻字符,用字母表中下一个字符替换它们,并遍历字符串,直到没有重复的字符。在“zz”的情况下,它应该以循环的方式回到“a”。字符串只能包括字符a-z,也就是说,没有大写字母或非字母字符。我已经写了一个函数来做这件事,但它是不够有效的一个非常长的字符串。
def solve(s):
i = 1
while i < len(s):
if s[i] == s[i-1]:
r = s[i+1:]
l = s[:i-1]
if s[i] == "z":
x = "a"
else:
x = chr(ord(s[i])+1)
i = 1
s = l+x+r
else:
i += 1
return s
例如,如果s = 'aabbbc'
,函数应该像aabbbc --〉bbbc--〉cbbc--〉ccc一样工作,最后返回dc,我怎样才能使它更有效呢?
编辑:例如,如果s = 'ab'*10**4 + 'cc'*10**4 + 'dd'*10**4
,则此函数将花费大量时间。
5条答案
按热度按时间umuewwlo1#
作为一个简单的优化:可以使用软复位
i = i-1
来代替硬复位i = 1
。实际上,如果在转换之前1和i之间没有重复,那么在转换之后1和i-1之间仍然不会有任何重复。7dl7o3gd2#
你可以把结果构建成一个列表,像堆栈一样使用,添加与上一个不同的字母,并在它们匹配时弹出下一个字母:
输出:
u3r8eeie3#
我认为,在这种情况下,更容易想到的是在现有字符串的右边逐个添加新的字母,如果在任何时候我们在末尾遇到两个相同的字符,那么我们将它们都删除,并按字母顺序添加下一个字符。
这样,您将避免索引错误和复制整个字符串后,每次修改.
另外,注意由于每个操作都会减少结果的长度,所以复杂度将是O(len(s))。
olqngx594#
这里减速的主要原因是O(N^2)行为,其原因是:
s
的重复切片和重新创建(每次都必须分配和复制一个新的字符串,因为Python的字符串是不可变的)。s
的开始,这样它必须再次读取所有非重复的字母。例如,当减少'abcdeffhh'
时,在将'ff'
组合成'g'
之后,下一次迭代将在考虑'hh'
之前扫描所有非重复的字母。(这在Stef的回答中已经指出)我想到了与Alain T.相同的基本方法,尽管我可以提供一些微观优化。首先,我想解释一下为什么这种基于堆栈的方法要快得多:
.pop
是O(1);该列表是可变的,因此它只需要清除对最后一个对象的引用并更新其元素计数。然而,从任何其他位置删除元素都涉及将所有后续元素下移以填充差距。)类似地,由于新元素将进入单独的数据结构,因此永远不需要重新创建s
字符串。range
对象进行索引。(它将与栈顶比较)。(通常,遍历列表的重叠对可以also be done much more elegantly,但是标准方法假设输入不会被修改。当在输入序列上迭代are a bad idea和hard to get right时试图修改输入序列的算法)。maketrans
,可以很容易地创建这样的查找表;它适用于字符串的translate
方法,该方法将查找应用于字符串的每个字符,然而,由于我们在这部分只处理单字符字符串,因此构建一个直接将字母Map到字母的字典并正常使用它也同样有效。(str.maketrans
生成一个字典,但它将整数(基本上是ord
的结果)Map到其他整数或None
。)当然,“实用性胜过纯粹性”,所以没有测试这些都不重要,我制作了一个文件
q75690334.py
来测试所有这些方法的性能:验证正确性:
(noAssert被提出)
在命令行使用
n == 10**3
计时:然而,我想在这里强调的一点是,Stef的方法仍然涉及O(N^2)行为(因为切片的缘故)--只是需要更长的时间才能显现出来:
在这里,我们看到基于堆栈的方法对于10倍的输入花费了大约10倍的时间,但是改进的切片方法花费了将近40倍的时间。(渐进地,它最终应该花费100倍的时间;但足够长的输入来使其清楚,将花费太长的时间来测试。)
crcmnpdw5#
我希望提供一个Ruby的解决方案。考虑到Ruby和Python有许多相似之处,大多数读者至少应该能够了解我正在做的事情的要点。我发布这个答案是希望读者可能会喜欢它,并发布一个等效的(如果不是改进的话)Python解决方案。
首先创建一个保存正则表达式的常量。
此表达式匹配一对相同的字符(例如
"aa"
)。.
匹配保存到捕获组1的任何字符(行终止符除外)。\1
匹配捕获组1的内容。接下来,我将定义一个常量
H
,该常量保存一个哈希值,该哈希值将字母对Map到它们的“后继者”,例如"aa"->"b"
和"zz"->"a"
。x一个一个一个一个x一个一个二个x
现在我们可以定义一个方法,它将字符串作为参数,并返回所需的字符串。
我们试试看。
可以看出,每次迭代时
str
的值如下。现在我将分解该方法的每一步。
首先创建一个包含字母表中字母的数组。
'a'..'z'
表示一个范围,Range#to_a方法将其Map到一个数组中。散列
H
的构造分为四个步骤。这些步骤使用的方法有Array#map、String#*、Array#rotate、Array#zip和Array#to_h。
现在将给定的字符串赋给变量
str
。Ruby的方法Kernel#循环或多或少地循环到
end
语句,直到遇到break
关键字。它使用Ruby的String#sub!方法的形式,用它的第一个参数(字符串或正则表达式)匹配子字符串,并用它的第二个参数(散列)进行替换。这个方法修改字符串的位置。如果进行了替换,则返回字符串;else返回
nil
(Ruby有一个非破坏性的对应方法sub
,以及用于进行多次替换的方法gsub!
和gsub
)。最初尝试将
str
与正则表达式RGX
匹配。结果与"rr"
匹配。由于H["rr"] #=> "s"
,"s"
将替换"rr"
并返回修改后的字符串(以及在适当的地方改变),所以我们重复这个循环。这个循环一直持续到sub!
返回nil
,这时我们完成了,所以我们跳出循环并返回str
的当前内容。