异序词算法分析
要展示不同数量级的算法,一个好例子就是经典的异序词检测问题。
如果一个字符串只是重排了另一个字符串的字符,那么这个字符串就是另一个的异序词,比如heart与earth,以及python与typhon。为了简化问题,假设要检查的两个字符串长度相同,并且都是由 26 26 26个英文字母的小写形式组成的。我们的目标是编写一个布尔函数,它接受两个字符串,并能判断它们是否为异序词。
1. 清点法
清点第 1 1 1个字符串的每个字符,看看它们是否都出现在第 2 2 2个字符串中。如果是,那么两个字符串必然是异序词。清点是通过用Python中的特殊值 N o n e None None 取代字符来实现的。但是,因为Python中的字符串是不可修改的,所以先要将第 2 2 2个字符串转换成列表。在字符列表中检查第1个字符串中的每个字符,如果找到了,就替换掉。
def anagramSolution1(s1, s2):
alist = list(s2)
pos1 = 0
stillOK = True
while pos1 < len(s1) and stillOK:
pos2 = 0
found = false
while pos2 < len(s2) and not found:
if s1[pos1] == alist[pos2]:
found = True
else:
pos2 = pos2 + 1
if found:
alist[pos2] = None
else:
stillOK = False
pos1 = pos1 + 1
return stillOK
在该算法中,对于s1中的
n
n
n个字符,检查每一个时都要遍历s2中的
n
n
n个字符。要匹配s1中的一个字符,列表中的
n
n
n个位置都要被访问一次。因此,访问次数就成了从
1
1
1到
n
n
n的整数之和。这可以用以下公式来表示。
∑
i
=
1
n
i
=
n
(
n
+
1
)
2
=
1
2
n
2
+
1
2
n
\sum_{i=1}^{n}i=\frac{n(n+1)}{2}=\frac{1}{2}n^2+\frac{1}{2}n
i=1∑ni=2n(n+1)=21n2+21n
这个方案的时间复杂度是
O
(
n
2
)
O(n^2)
O(n2)。
2. 排序法
尽管s1与s2是不同的字符串,但只要由相同的字符构成,它们就是异序词。基于这一点,可以采用另一个方案。如果按照字母表顺序给字符排序,异序词得到的结果将是同一个字符串。
在Python中,可以先将字符串转换为列表,然后使用内建的sort()方法对列表排序。
def anagramSolution2(s1, s2):
alist1 = s1
alist2 = s2
alist1.sort()
alist2.sort()
pos = 0
matches = True
while pos < len(s1) and matches:
if alist1[pos] == alist2[pos]:
pos = pos + 1
else:
matches = False
return matches
该算法中, s o r t ( ) sort() sort()排序的时间复杂度即为排序算法的时间复杂度,基本上为 O ( n 2 ) O(n^2) O(n2)或者 O ( n ∗ l n ( n ) ) O(n*ln(n)) O(n∗ln(n)),排序后遍历一次比较 n n n个字符时间复杂度为 O ( n ) O(n) O(n),故总时间复杂度主要取决于sort()函数所采用的排序算法。
3. 穷举法
可以用s1中的字符生成所有可能的字符串,看看s2是否在其中。
时间复杂度为 O ( n ! ) O(n!) O(n!),算法效果过差,故不再提供代码示例。
4. 计数法
该算法基于以下事实:
两个异序词有同样数目的
a
a
a、同样数目的
b
b
b、同样数目的
c
c
c,等等。
要判断两个字符串是否为异序词,先数一下每个字符出现的次数。因为字符可能有 26 26 26种,所以使用 26 26 26个计数器,对应每个字符。每遇到一个字符,就将对应的计数器加 1 1 1。最后,如果两个计数器列表相同,那么两个字符串肯定是异序词。
def anagramSolution4(s1, s2):
c1 = [0] * 26
c2 = [0] * 26
for i in range(len(s1)):
pos = ord(s1[i]) - ord("a")
c1[pos] = c1[pos] + 1
for i in range(len(s2)):
pos = ord(s2[i]) - ord("a")
c2[pos] = c2[pos] + 1
j = 0
stillOK = True
while j < 26 and stillOK:
if c1[j] == c2[j]:
j = j + 1
else:
stillOK = False
return stillOK
前两个计数循环都是 n n n阶的。第 3 3 3个循环比较两个列表,由于可能有 26 26 26种字符,因此会循环 26 26 26次。全部加起来,得到总步骤数 T ( n ) = 2 n + 26 T(n)=2n+26 T(n)=2n+26 ,即时间复杂度为 O ( n ) O(n) O(n)。
但是,该算法要用额外的空间来存储寄存器,即用空间换时间。