【问题标题】:My Python code pass simple tests but not extended ones due to performance constrains由于性能限制,我的 Python 代码通过了示例测试,但没有通过扩展测试
【发布时间】:2020-06-01 02:37:20
【问题描述】:

我正在自学使用 Python 编码,并且我已经在 Codewars 上完成了一段时间的 kata(目前主要是 7 kyu)。在尝试升级时偶然发现了following 6 kyu kata,据我所知,这似乎是可以实现的:

完成将字符串作为输入的函数,并返回所有未配对字符的列表(即它们在字符串中出现奇数次),按照它们作为数组遇到的顺序。

如果有多个出现可供选择,则取最后出现的未配对字符。

注意事项:

使用的字符范围很广,其中一些字符可能无法在您的浏览器中正确呈现。 您的解决方案在字符串长度方面应该是线性的,才能通过最后的测试。 例子:

"Hello World" --> ["H", "e", " ", "W", "r", "l", "d"]

“代码战”--> [“C”、“o”、“d”、“e”、“w”、“a”、“r”、“s”]

"woowee" --> []

"wwoooowweeee" --> []

“赛车”--> [“e”]

“妈妈”--> [“M”]

"妈妈" --> ["M", "m"]

以下 Python 3.6.0 代码通过示例测试:

def function_name(s):
    result = []
    lengthS = len(s)
    dictResult = {}

    for item in s[-1:(-1 * lengthS - 1):-1]:
        numLetters = 0
        for number in range(lengthS):
            if item == s[number]:
                numLetters = numLetters + 1
        if numLetters % 2 != 0:
            dictResult[item] = numLetters

    for key in list(dictResult.keys())[-1:(-1 * (len(list(dictResult))) - 1):-1]:
        result.append(key)

    return result

由于以下原因,运行一整轮测试失败:

执行超时(12000 毫秒)

注释代码的某些部分可以让我更快地失败整轮测试,因此我可以看到测试使用很长的字符串。

很明显,我的代码没有进行性能优化。阅读一些关于算法的文章,我怀疑我的代码由于在另一个 for 循环中的 for 循环而缺乏性能,这是 O(n²) 复杂度操作。这是我坚持了一段时间的地方。我不确定这是否是唯一需要更改的代码,如果是 - 只是无法理解如何做到这一点。

注意。请不要发布完整解决此问题的答案。我宁愿自己想出最终的代码。因此,如果您能指出我的代码中需要注意的部分并提供建议、示例代码或进一步阅读,我将非常高兴。

【问题讨论】:

  • 嗨,Alex,IMO 认为您的问题非常适合代码审查codereview.stackexchange.com,您应该试一试。
  • @Alex 你能分享链接吗?
  • @RomainL。我认为这是一个话题,因为 OP 还没有一个可行的解决方案。 meta.stackexchange.com/questions/102852/…
  • @vivek_23 没错,但是这段代码(和 OP)无论如何都可以从代码审查中受益。
  • @vivek_23 更新问题以包含 URL

标签: arrays python-3.x string algorithm performance


【解决方案1】:

根据您的说明:

注意。请不要发布带有完整解决方案的答案 问题。我宁愿自己想出最终的代码。所以我会 如果您能指出我的代码部分需要 关注建议、示例代码或进一步阅读。

  • 我只会发布瓶颈,其余的由你来解决。 你的代码有

    for number in range(lengthS):
            if item == s[number]:
                numLetters = numLetters + 1
    

这基本上意味着您正在遍历整个字符串并检查所有item 字符存在的位置并收集计数。这使您的代码本质上是二次的(O(n^2)),因为对于每个字符,字符串中都有一个完整的扫描,渐近地采取 n^2 步。

为了克服这个问题,您可以在迭代字符串本身的同时通过字典方法的帮助来收集计数。因此,您的代码可以从

for item in s[-1:(-1 * lengthS - 1):-1]:
        numLetters = 0
        for number in range(lengthS):
            if item == s[number]:
                numLetters = numLetters + 1
        if numLetters % 2 != 0:
            dictResult[item] = numLetters

for item in s[-1:(-1 * lengthS - 1):-1]:
        dictResult[item] = dictResult.get(item,0) + 1

这将在一次传递中收集特定角色的所有计数,使其本质上是 O(n)。

  • 我发现的第二个观察结果是反向遍历字符串没有任何意义(或任何优势),如下所示:

    for item in s[-1:(-1 * lengthS - 1):-1]:
    

    您可以尝试简单地逐个遍历它们,如下所示:

    for item in s:
        dictResult[item] = dictResult.get(item,0) + 1
    
  • 第三是观察你是否真的需要dictResult中的任何item,如果它的计数是偶数。如果任何字符的计数在迭代中的任何时候都是偶数,您可以简单地将其从dictResult 中删除。这样,您在dictResult 中将只有奇值键。这也使您的代码更节省空间(有时很重要)。 请注意,dictResult 中的奇数值键不会按照预期输出的字符顺序进行排序。我把这个作为练习留给你去解决。

更新:

似乎从Python3.7开始,字典中的键是按照插入的顺序存储的。所以,匹配输出的顺序还是比较容易的。

正确的片段:

def odd_one_out(s):
    result = []
    dictResult = {}

    for item in s:
        dictResult[item] = dictResult.get(item,0) + 1
        if dictResult[item] % 2 == 0:
            dictResult.pop(item)
    return list(dictResult.keys())

【讨论】:

  • 谢谢!用字典方法交换两个“for”循环并进行一些其他调整,从而在约 3000 毫秒内通过扩展测试。我有类似的逻辑,但无法弄清楚这个简单的代码。关于您的第二次观察。任务要求“如果有多个外观可供选择,则取最后一次出现的未配对字符”,如果是“Hello World”,它将保持字典中第一次出现的字母“l”,而不是最后一个。所以从最后一个字符迭代字符串对我来说更有意义。
  • @Alex 很高兴为您提供帮助。如果您随后进行第三次观察,您将始终在字典中仅设置最后一次出现的 l。您可以在这篇文章的更新中看到我接受的解决方案。
  • 字典的键是从python3.7开始按顺序存储的!在 python3.6 中,CPython 中有一个实现细节导致了相同的行为,但对于 pypy 等其他解释器(通常由 OP 提到的那种平台使用)来说,情况并非如此。
  • @MarcoZamboni 感谢您的更新 :) 不是 python 人,所以不知道。再次感谢 Python 版本更新
【解决方案2】:

您正确地确定了导致超出时间限制的错误是 O(n^2)。

除了使用两个 for 循环来检查每个字符的出现情况之外,您能想出一种方法来简单地遍历字符串并存储它吗?

这将大大减少执行时间。

就空间复杂度而言,您是正确的。

【讨论】:

    【解决方案3】:

    您可以尝试的一件事是使用 dict 来计算每个字母的出现次数。这样你只会遍历你的字符串一次。

    示例:

    my_counter = {}
    for letter in s:
        my_counter[letter] = my_counter.get(letter, 0) + 1 
    

    因为您不想要完整的解决方案,所以我让您计算其余部分。希望对您有所帮助。

    get() 的文档: https://docs.python.org/3/library/stdtypes.html?highlight=get#dict.get

    【讨论】:

      【解决方案4】:

      这里我用一点点python和他的标准库的知识给出一个简洁的解决方案:

      这适用于 python>=3.7,因为在此之前 Counter 未定义为有序

      from collections import Counter
      def function_name(s):
          count = Counter(s)
          answer = [c for c, num in count.items() if num % 2]
          return answer
      

      对于其他版本,从字符串中按顺序进行字符更改:

      from collections import Counter
      def function_name(s):
          count = Counter(s)
          answer = [c for c in s if count[c] % 2]
          return answer
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2019-10-05
        • 2021-09-25
        • 2013-08-24
        • 2019-06-23
        • 1970-01-01
        • 2013-11-18
        • 1970-01-01
        相关资源
        最近更新 更多