【问题标题】:Returning Boolean value error when traversing a list遍历列表时返回布尔值错误
【发布时间】:2020-02-16 06:16:32
【问题描述】:

我试图找出一个列表是否是一个严格递增的序列,如果 1 并且只有 1 个元素被删除。这适用于某些列表,但不适用于其他列表之间没有任何明显差异的列表。对于大型列表,它超过了执行时间限制。这是我的代码:

def almostIncreasingSequence(sequence):

    for i in range(len(sequence)):
        new_seq = sequence.copy()
        del new_seq[i]

        if all(i < j for i, j in zip(new_seq, new_seq[1:])):
            output = True
        else:
            output = False

    return output

我正在创建输入列表的副本。然后,我删除元素i,然后返回TrueFalse,具体取决于列表是否是严格递增的序列。我正在 for 循环中创建列表的副本,以确保我只删除一个元素。以下是代码未返回适当值的一些测试运行:

Input:
sequence: [10, 1, 2, 3, 4, 5]
Output: false
Expected Output: true

Input:
sequence: [1, 2, 5, 3, 5]
Output: false
Expected Output: true

并且在这个测试用例中,代码超过了执行时间限制:

Input:
sequence: [-9996, -9995, -9994, -9993, -9991, -9989, -9987, -9986, -9985, -9983, -9982, -9980, -9978, -9977, -9976, -9975, -9974, -9972, -9968, -9966, -9965, -9961, -9957, -9956, -9955, -9954, -9952, -9948, -9942, -9939, -9938, -9936, -9935, -9932, -9931, -9927, -9925, -9923, -9922, -9921, -9920, -9919, -9918, -9908, -9905, -9902, -9901, -9900, -9899, -9897, -9896, -9894, -9888, -9886, -9880, -9878, -9877, -9876, -9874, -9872, -9871, -9870, -9869, -9868, -9867, -9865, -9857, -9856, -9855, -9854, -9853, -9852, -9851, -9849, -9848, -9846, -9845, -9843, -9842, -9841, -9840, -9837, -9834, -9828, -9826, -9824, -9823, -9820, -9816, -9814, -9812, -9811, -9810, -9809, -9807, -9806, -9804, -9803, -9801, -9800]
Output: undefined
Expected Output: false

【问题讨论】:

  • 性能问题很可能是算法的选择,而不是程序本身。

标签: python algorithm list for-loop


【解决方案1】:

您的循环将始终覆盖(返回 False 或 True)每次迭代中的先前结果。所以它实际上“忘记”了前一次迭代中的任何违规行为。只有最后一次迭代决定了输出,所以你的算法是错误的。

其次,即使您会修复该错误(如果新列表未排序,则通过退出循环),此算法表示时间复杂度 O(n²),因为在每个迭代中:

  • 使用.copy() 制作列表的副本
  • 执行了del,需要移动它后面的所有值
  • 使用new_seq[1:] 制作列表的新副本
  • zip 被调用,创建另一个列表
  • all 被调用,迭代新列表

所有这些操作都在扼杀性能。

相反,您应该只对列表执行 一个 迭代,并在没有任何嵌套迭代的情况下解决问题。一条线索是您实际上不必执行删除元素。您只需要检查情况就好像它被删除了

一个高效的算法只需要查看三个值:当前迭代的值和前面的两个值。如果这三个不按顺序排列,您可以确定应该删除哪一个,但仅在这三个变量中表示该删除(保持列表不变):

def almostIncreasingSequence(sequence):
    beforePrev = prev = float('-inf')
    allowExceptions = True

    for curr in sequence:
        if curr <= prev: # Order is not maintained:
            if not allowExceptions: # It's not the first time
                return False 
            allowExceptions = False
            # Decide whether to skip the current or previous value
            if curr > beforePrev:
                prev = curr;
        else: # Normal case: keep track of two preceding values
            beforePrev, prev = prev, curr
    return True

说明

在列表的遍历过程中,三个列表值分别保存在单独的内存中:currprevbeforePrev。后两者落后于curr 值。所以在每次迭代中,curr 获取当前列表值,并且在迭代结束时,我们移动值:beforePrev ← prev ← curr。所以本质上,这三个值对应于最近访问的 3 个值——至少当列表中没有异常时。

假设在某个时刻你发现了一个异常:你找到了curr &lt;= prev 的位置,即序列不是严格递增的位置;那么很明显必须删除其中一个值:应该删除currprev 以希望列表可以“修复”。

可能是算法已经决定必须在较早迭代中删除一个值(allowExceptionsFalse):但我们只允许删除 列表中的一个值,因此在这种情况下,我们认为没有解决方案并返回False

但如果这是我们第一次遇到这种情况,那么我们应该决定是否将currprev 识别为违规值。我们可以假设beforePrevprev 的顺序是正确的(我们在前面的迭代中验证了这一点),所以对于beforePrev prevcurr 的相对顺序实际上只剩下两种可能性:要么curr &gt; beforePrev,要么不。

如果curr &gt; beforePrev,那么这两个相对于彼此的顺序很好,所以如果我们从这两个之间删除prev,我们会将列表的那部分恢复为递增顺序。

如果curr &lt;= beforePrev,那么它们之间的顺序不是很好,所以我们应该删除curr来恢复递增的顺序。

为了节省时间,我们实际上不会执行从列表中删除所选项目:我们只会将删除的结果应用于变量 beforePrevprev 和 @ 987654353@,这样在下一次迭代中它们的值就好像列表中删除了一个项目:

当我们删除一个元素时,beforePrev 在循环的下一次迭代中应该保持不变:所以我们不会像往常那样触及它的值。

如果我们删除prev,那么新的prev 就变成了现在的curr。如果我们删除curr,则无事可做:prev 在下一次迭代中也保持不变,curr 将在下一次迭代开始时以任何方式获取其新值。

因为我们“删除”了一个项目,所以我们还将allowExceptions 设置为False,这样我们就知道我们不允许执行第二次删除。

仍然要说的是,在第一次迭代中,我们并没有真正的beforePrevprev,所以我们将它们设置为-infinity,这样在第一次迭代中就不会决定删除。

【讨论】:

  • 我同意你的方法,但我根本不理解你的代码..
  • 我在回答中添加了解释。让我知道这是否可以为您澄清。请注意,您的原始代码(以及另一个答案中的代码)将花费太多时间来成为有效的解决方案。在较大的输入上,它们会超时。
【解决方案2】:

您正在返回最后一次检查的值,即当您删除列表中的最后一项时。将其更改为在第一个匹配项中返回 TrueFalse 如果没有匹配项

def almostIncreasingSequence(sequence):

    for i in range(len(sequence)):
        new_seq = sequence.copy()
        del new_seq[i]

        if all(i < j for i, j in zip(new_seq, new_seq[1:])):
            return True

    return False

【讨论】:

  • 谢谢,但这并不能解决执行时间限制问题。
  • @OnurOzbek 你是不是在某个在线工具上运行它?
  • @OnurOzbek 我不熟悉它,但是在线编译器通常有几秒钟的时间限制,尽管运行时间应该不到一秒钟。有额外的代码吗?
  • 不。这就是全部。
猜你喜欢
  • 2015-01-21
  • 2017-07-31
  • 2020-06-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-02-13
  • 2021-11-13
相关资源
最近更新 更多