您的算法时间复杂度为O(kn)
,其中k
是字符串中唯一字符的数量。如果k
是一个常数,那么它就是O(n)
。由于问题描述明确定义了元素的替代方案数量的上限(“假设是小写(ASCII)字母”),因此k
是一个常数,您的算法在这个问题上运行时的时间复杂度是O(n)
。即使n会无限增长,您也只会对字符串进行O(1)
次切片,因此您的算法始终保持为O(n)
。如果移除track
,则它将成为O(n²)
:
In [36]: s = 'abcdefghijklmnopqrstuvwxyz' * 10000
In [37]: %timeit firstUniqChar(s)
100 loops, best of 3: 18.2 ms per loop
In [38]: s = 'abcdefghijklmnopqrstuvwxyz' * 20000
In [37]: %timeit firstUniqChar(s)
10 loops, best of 3: 36.3 ms per loop
In [38]: s = 'timecomplexity' * 40000 + 'a'
In [39]: %timeit firstUniqChar(s)
10 loops, best of 3: 73.3 ms per loop
在这里,T(n)
的时间复杂度依然是O(n)
,它与字符串中字符数量呈线性关系,即使这是算法的最坏情况 - 没有任何一个字符是唯一的。
我将介绍一种不太高效但简单且智能的方法:首先使用collections.Counter
计数字符直方图,然后遍历字符找到其中一个
from collections import Counter
def first_uniq_char_ultra_smart(s):
counts = Counter(s)
for i, c in enumerate(s):
if counts[c] == 1:
return i
return -1
first_uniq_char('timecomplexity')
这个算法的时间复杂度为
O(n)
;
Counter
在
O(n)
时间内计数直方图,我们需要再次枚举字符串中的
O(n)
个字符。然而,在实践中,我相信我的算法具有较低的常数,因为它使用了标准字典来计数
Counter
。
让我们设计一个非常愚蠢的暴力算法。由于可以假设字符串仅包含小写字母,因此利用这一假设:
import string
def first_uniq_char_very_stupid(s):
indexes = []
for c in string.ascii_lowercase:
if s.count(c) == 1:
indexes.append(s.find(c))
return min(indexes, default=-1)
让我们在Python 3.5上测试我的算法和其他答案中发现的一些算法。我选择了一个对我的算法来说非常糟糕的情况:
In [30]: s = 'timecomplexity' * 10000 + 'a'
In [31]: %timeit first_uniq_char_ultra_smart(s)
10 loops, best of 3: 35 ms per loop
In [32]: %timeit karin(s)
100 loops, best of 3: 11.7 ms per loop
In [33]: %timeit john(s)
100 loops, best of 3: 9.92 ms per loop
In [34]: %timeit nicholas(s)
100 loops, best of 3: 10.4 ms per loop
In [35]: %timeit first_uniq_char_very_stupid(s)
1000 loops, best of 3: 1.55 ms per loop
因为它在结尾找到了
a
并退出,所以我的愚蠢算法是最快的。而我的聪明算法最慢。除了这种最坏情况外,我的算法表现不佳的另一个原因是,在Python3.5上,
OrderedDict
用C语言编写,而
Counter
则使用Python语言。
让我们进行更好的测试吧:
In [60]: s = string.ascii_lowercase * 10000
In [61]: %timeit nicholas(s)
100 loops, best of 3: 18.3 ms per loop
In [62]: %timeit karin(s)
100 loops, best of 3: 19.6 ms per loop
In [63]: %timeit john(s)
100 loops, best of 3: 18.2 ms per loop
In [64]: %timeit first_uniq_char_very_stupid(s)
100 loops, best of 3: 2.89 ms per loop
看起来,我的“愚蠢”算法并不那么愚蠢,它利用了C语言的速度,同时最大程度地减少了运行Python代码的迭代次数,在这个问题上明显获胜。
abbacdec
,算法会返回a
(它不会 _重复_),还是返回d
(它只出现 _一次_)? - miraculixx