是否可以在不使用排序优于O(n ^ 2)的sorted()或字典的情况下检查anagram?

时间:2018-01-11 23:33:44

标签: python algorithm

我正在学习,我被告知不要在python中使用像sorted()或字符串拆分这样的“魔术”python技巧。

我误以为这只是使用数组来检查输入字符串是否是一个字谜。因此,虽然我想到的第一件事是使用字典我没有追求它,因为我认为它被禁止了。使用字典我可以通过使用字母作为键并将计数作为值来计算字母的频率,并倒计数(减去)输入字符串中遇到的字母的频率并为频率字典执行循环查看如果有任何零。

所以...对我设置限制的错误概念,我创建了一个如下所示的嵌套循环(问题假设长度相等,没有空格)

def anagram(original, input):
    for a in original:
        i=0
        for b in input:
            if a == b:
               del input[i]
            else:
               pass
        i+=1
    if len(input) == 0:
        return True
    else:
        return False

显然这是不可取的,因为大的符号是O(n ^ 2)到使用字典的解决方案是O(3n),两次迭代来计算频率,最后的迭代来检查是否有任何条目字典具有非零频率(这意味着这不是一个字谜)。

所以这是我最终的一些理解问题,但不是继续前进,我想我自己是否有可能产生一个比我的解决方案O(n ^ 2)更好的表现更好的anagram检查器,而不使用字典而只是依赖在数组/列表?

我有另一个想法,但我停了下来:

1)将字符串字符列表转换为数字列表 - 但这意味着我仍然需要在引用字符(原始字符)上循环以查找数字位置。

它一直在吃掉我,我意识到我正在推翻这些简单的算法问题......但是如果有一个符合标准的解决方案仍然很好奇。

6 个答案:

答案 0 :(得分:2)

回答这个问题的pythonic方法是使用collections.Counter对象:

from collections import Counter

def anagram(s1, s2):
    return Counter(s1) == Counter(s2)

但是由于这些是受限制的,你可以回归到香草词典(也称为哈希图,这是许多有效算法的基本要素)。

高级过程如下。首先,为string1构建计数的哈希映射。重复string2的过程。最后,比较两个哈希映射是否相等。

首先,辅助函数 -

def build_counts(string):
    ctr = {}
    for c in string:
        ctr[c] = ctr.setdefault(c, 0) + 1

    return ctr

现在,司机 -

def anagram(string1, string2):  
    c1 = build_counts(string1)
    c2 = build_counts(string2)

    return c1 == c2

复杂性分析 - 构建每个散列映射需要O(N)时间,并且执行比较也是O(N),因为您必须,一个,测试密钥是否相同,以及两个,比较相应密钥的值。总而言之,线性算法。

由于散列图和散列集是如此常见,所以你不应该认为这是受限制的,除非你打算用数组和开放寻址实现你自己的散列映射。

不,没有高效的算法不依赖于哈希映射或更复杂的东西。除非您使用viraptor的答案,它基本上是一个阵列版本的hashmap (!),但在ASCII集中为每个字符都有一个唯一的条目。例如,ASCII字符65的计数将使用arr[65]访问,依此类推。所以,你需要一个足够大的数组来适应每个ASCII字符。

只是 ASCII字母的事情是可以管理的,但是当您考虑其他更广泛的编码(unicode)时,情节会变粗。最后,使用hashmap的空间效率要高得多。

答案 1 :(得分:1)

这是一种在线性时间内工作的替代方法,用于“合理的”#34;长话。如果你不计算任意精度乘法,算法运行O(n)。

逻辑,如果您将每个字母分配给素数。这些素数乘以2个字谜将是相同的。

我希望减少并不算作神奇的功能。

from operator import mul
from functools import reduce


def is_anagram(word_a, word_b):
    primes_26 = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37,
                 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101]

    def prime_product(word):
        return reduce(mul, [primes_26[ord(ch) - 65] for ch in word.upper() if ch.isalpha()])

    return prime_product(word_a) == prime_product(word_b)


assert is_anagram("abc", "cba") 
assert not is_anagram("abc", "cbad") 

答案 2 :(得分:0)

这取决于您输入的定义。您想要处理所有字符,还是只处理可打印/拉丁-1设置?您是否关心理论复杂性或真实表现?

对于第一个问题 - 如果你不关心字符编码到多个字节,你可以创建一个包含256个元素的列表,而不是索引到dict,而是索引该数组。对于每个字符,在列表中的特定位置添加/删除1。它与您的dict解决方案的复杂性相同:O(n+m)。 (在数组中计数是O(1),因为它有预定义的大小)

对于第二个问题 - 如果你想使用没有限制的字符,你可以做同样的事情,但是创建一个带有1,114,112元素的列表和一个unicode字符编号的索引。它不会比字典解决方案更快,但同样 - 复杂性仍然是O(n+m)

答案 3 :(得分:0)

如果不允许使用魔术功能,您可以构建自己的计数器

def Counter(string): return {i: string.count(i) for i in set(string)}

然后你可以简单地回到cᴏʟᴅsᴘᴇᴇᴅ的解决方案

答案 4 :(得分:0)

def check_anagram(data1,data2):
    
    flag = False
    if (len(data1)==len(data2)):
        if (set(data1.lower()) == set(data2.lower())):
            for i in range(len(data1)-1):
                if data1[i] != data2[i]:
                    flag = True

    return flag

print(check_anagram("Theclassroom","Schoolmaster"))

答案 5 :(得分:0)

通过比较所有 26 个字母并计算它们,得出 T(n)=2n+26。

给出 O(n)

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

print(anagramSolution4('apple','pleap'))