【问题标题】:Find longest quasi-constant sub-sequence of a sequence查找序列的最长准常数子序列
【发布时间】:2018-09-10 15:10:06
【问题描述】:

我今天早些时候进行了这个测试,我试图太聪明并遇到了障碍。不幸的是,我陷入了这种思维定势,浪费了太多时间,未能通过这部分测试。后来我解决了,但也许你们都可以帮助我摆脱最初的陈规。

问题定义:

给出了一个由 N 个整数(都是正数)组成的无序且非唯一的序列 A。 A 的子序列是通过从 A 中删除任何元素、部分或全部元素而获得的任何序列。序列的幅度是该序列中最大元素和最小元素之间的差。假设空子序列的幅度为0。

例如,考虑由六个元素组成的序列 A:

A[0] = 1
A[1] = 7
A[2] = 6
A[3] = 2
A[4] = 6
A[5] = 4

如果数组 A 的幅度不超过 1,则称为准常数。在上面的示例中,子序列 [1,2]、[6,6] 和 [6,6,7 ] 是准常数。子序列[6,6,7]是A的最长可能的准常子序列。

现在,找到一个解决方案,给定一个由 N 个整数组成的非空零索引数组 A,返回数组 A 的最长准常数子序列的长度。例如,给定上述序列 A ,函数应该返回 3,正如解释的那样。

现在,我在 python 3.6 中使用没有类的基于排序的方法解决了这个问题(我的代码在下面),但我最初不想这样做,因为对大型列表进行排序可能会非常慢。看起来这应该有一个相对简单的公式作为广度优先的基于树的类,但我做错了。对此有什么想法吗?

我的基于无类排序的解决方案:

def amp(sub_list):
    if len(sub_list) <2:
        return 0
    else:
        return max(sub_list) - min(sub_list)

def solution(A):
    A.sort()
    longest = 0
    idxStart = 0
    idxEnd = idxStart + 1
    while idxEnd <= len(A):
        tmp = A[idxStart:idxEnd]
        if amp(tmp) < 2:
            idxEnd += 1
            if len(tmp) > longest:
                longest = len(tmp)
        else:
            idxStart = idxEnd
            idxEnd = idxStart + 1
    return longest

【问题讨论】:

  • 你说“在大列表上排序可能很慢”,但排序有时间复杂度O(n log n) 并且高度优化。大多数树算法具有相同的时间复杂度并且没有被优化。为什么你认为树算法会比基于排序的算法更好? (一个好的基于排序的解决方案是排序后的O(n)。)
  • 我猜你说得很好。看来我肯定是想多了这个问题。

标签: python tree subsequence


【解决方案1】:

正如 Andrey Tyukin 所指出的,您可以在 O(n) 时间内解决此问题,这比您可能从排序或任何基于树的解决方案中获得的 O(n log n) 时间要好。诀窍是使用字典计算输入中每​​个数字的出现次数,并使用计数找出最长的子序列。

我和他有类似的想法,但我的实现方式略有不同。经过一些测试,看起来我的方法要快得多,所以我将其发布为我自己的答案。好短啊!

from collections import Counter

def solution(seq):
    if not seq:     # special case for empty input sequence
        return 0
    counts = Counter(seq)
    return max(counts[x] + counts[x+1] for x in counts)

我怀疑这比 Andrey 的解决方案更快,因为我们两个解决方案的运行时间确实需要 O(n) + O(k) 时间,其中 k 是输入中 不同 值的数量(以及 @987654326 @ 是输入中值的总数)。我的代码通过将序列传递给用 C 实现的 Counter 构造函数来非常有效地处理 O(n) 部分。处理 @987654329 可能会慢一些(基于每个项目) @ 部分,因为它需要一个生成器表达式。 Andrey 的代码则相反(它对O(n) 部分运行较慢的Python 代码,而对O(k) 部分使用更快的内置C 函数)。由于k 总是小于或等于n(如果序列有很多重复值,可能会少很多),我的代码总体上更快。虽然这两种解决方案仍然是O(n),但两者都应该比对大输入进行排序要好得多。

【讨论】:

  • 这是一个详细的分析,再次感谢Counter的提示!
【解决方案2】:

我不知道 BFS 在这里应该如何提供帮助。

为什么不简单地遍历序列并计算每个可能的准常数子序列有多少元素?

from collections import defaultdict

def longestQuasiConstantSubseqLength(seq):
  d = defaultdict(int)
  for s in seq:
    d[s] += 1
    d[s+1] += 1
  return max(d.values() or [0])

s = [1,7,6,2,6,4]

print(longestQuasiConstantSubseqLength(s))

打印:

3

正如预期的那样。

解释:每个非常量准常数子序列都由它所包含的最大数唯一标识(只能有两个,取较大的一个)。现在,如果你有一个数字s,它可以贡献给以ss + 1 作为最大数字的准常数子序列。因此,只需将+1 添加到ss + 1 标识的子序列中。然后输出所有计数的最大值。

您无法比O(n) 更快地获得它,因为您必须至少查看输入序列的每个条目一次。

【讨论】:

  • 不错的解决方案。建议:d = defaultdict(lambda: 0)可以换成d = defaultdict(int);需要处理空seq:return max(d.values() or [0])
  • @Marat,感谢您的反馈!花了一点时间才明白 nullary int() 函数将 0 作为默认 int 生成。将在一秒钟内更新。
  • @Marat,已更新,希望增加 pythonicity。 :] 非常感谢!
  • 这相当于我打算使用collections.Counter 编写的解决方案。我的方法是只计算项目的实际值,但将max 调用中相邻键的值相加(例如max(counts[x]+counts[x+1] for x in counts))。两者都是O(n),我怀疑它们之间的性能差异会很大。
  • @Blckknght 谢谢 Blckknght,确实,Counter 就足够了。实际上,只需将defaultdict[k] += 1 替换为相应计数器的增量,就可以组合这两种解决方案。我对 python 集合的使用可能还没有 100% 足够,今天才了解defaultdict,尝试在某个地方使用它:] 典型的锤钉问题,实际上:D
猜你喜欢
  • 1970-01-01
  • 2020-04-14
  • 2021-09-02
  • 2017-02-25
  • 2015-12-10
  • 1970-01-01
  • 2017-03-21
  • 1970-01-01
  • 2016-07-17
相关资源
最近更新 更多