【问题标题】:Optimization of list's sublist优化列表的子列表
【发布时间】:2018-04-18 00:40:42
【问题描述】:

问题是从给定列表中查找不包含大于指定上限数字的子列表的总数,例如right,并且子列表最大数量应该大于下限,例如@987654324 @ .假设我的列表是:x=[2, 0, 11, 3, 0] 并且子列表元素的上限是 10 并且下限是 1 那么我的子列表可以是 [[2],[2,0],[3],[3,0]] 因为子列表总是连续的。我的脚本运行良好并产生正确的输出,但需要一些优化

def query(sliced,left,right):
    end_index=0
    count=0
    leng=len(sliced)
    for i in range(leng):
        stack=[]
        end_index=i

        while(end_index<leng and sliced[end_index]<=right):

            stack.append(sliced[end_index])
            if max(stack)>=left:
                count+=1
            end_index+=1

    print (count)

origin=[2,0,11,3,0]
left=1
right=10
query(origin,left,right)

output:4

对于一个列表来说,x=[2,0,0,1,11,14,3,5] 有效的子列表可以是 [[2],[2,0],[2,0,0],[2,0,0,1],[0,0,1],[0,1],[1],[3],[5],[3,5]],总数为 10

【问题讨论】:

  • 我想这在codereview上会更好?
  • 在您的示例中您说:我的子列表可以是 [[2]、[2,0]、[3]、[3,0]],为什么不是 [0,2] 或[0,3]?
  • @DRPK 因为子列表是连续的,并且子列表中第一个元素的索引必须小于其他元素,即它们需要按索引递增的顺序排列。
  • @Demonking28:因为你说:输出:4 并且我的脚本运行良好......在这种情况下输出应该是 [[0, 0], [2, 0], [0, 2] ,[0, 3], [3, 0], [3, 2], [2, 3]] 其计数为 7
  • @Demonking28。该问题不涉及特定编程问题。它只是询问有关优化某些任意代码(已经有效)的一般建议,而没有给出任何评估标准。 (PS:见the FAQ on this subject接受答案的最后一段)。

标签: python algorithm python-3.x optimization


【解决方案1】:

将数字分类为小、有效和大(S、V 和 L),并进一步索引有效数字:V_1、V_2、V_3 等。让我们从假设没有大数字开始。

考虑列表 A = [S,S,...,S,V_1, X,X,X,X,...X] 。如果 V_1 有索引 n,则有 n+1 个子集,形式为 [V_1] , [S,V_1], [S,S,V_1] 等等。对于这些 n+1 个子集中的每一个,我们可以附加 len(A)-n-1 个序列:[X]、[XX]、[XXX] 等等。给出总共 (n+1)(len(A)-n) 个包含 V_1 的子集。 但是我们可以将所有子集的集合划分为那些包含 V_k 但没有 V_n 的子集,因为 n 小于 k。因此,我们必须简单地使用 V_2 对列表的剩余 XXX…X 部分执行相同的计算并迭代。这需要这样的东西:

def query(sliced,left,right,total):
    index=0
    while index<len(sliced):
        if sliced[index]>=left:
            total+=(index+1)*(len(sliced)-index)
            return total+query(sliced[index+1:],left,right,0)
        else:
            index+=1
    return total

要合并大数,我们可以根据大数出现的位置对整个集合进行划分,并为每个集合添加序列的总数。如果我们调用我们的第一个函数 sub_query,那么我们会得到以下结果:

def sub_query(sliced,left,right,total):
    index=0
    while index<len(sliced):
        if sliced[index]>=left:
            total+=(index+1)*(len(sliced)-index)
            return total+sub_query(sliced[index+1:],left,right,0)
        else:
            index+=1
    return total

def query(sliced,left,right):
    index=0
    count=0
    while index<len(sliced):
        if sliced[index]>right:
            count+=sub_query(sliced[:index],left,right,0)
            sliced=sliced[index+1:]
            index=0
        else:
            index+=1
    count+=sub_query(sliced,left,right,0)
    print (count)

这似乎会遍历列表并减少检查最大值/最小值的次数。请注意,它不区分相同但来自原始列表中不同位置的子列表(就像从诸如 [0,1,0,0,1,0] 之类的列表中产生的那样。但是来自原始帖子也不会这样做,所以我猜这不是必需的。

【讨论】:

  • 附注这是我对 SO 的第一个回答。我很想知道如何使它更有帮助。非常欢迎建设性的 cmets :) 谢谢
  • 您的优化肯定更快,但对于多达 10**4 的大量查询,此解决方案仍不起作用!
【解决方案2】:

蛮力

生成每个可能的子列表并检查给定条件是否适用于每个子列表。

最坏的情况:对于数组中的每个元素eleft &lt; e &lt; right。 时间复杂度:O(n^3)

优化的蛮力(OP的代码)

对于数组中的每个索引,增量构建一个有效的临时列表(虽然不是真正需要)。

最坏情况:对于数组中的每个元素eleft &lt; e &lt; right。 时间复杂度:O(n^2)

更优化的解决方案

如果数组有n 元素,那么数组中子列表的数量是1 + 2 + 3 + ... + n = (n * (n + 1)) / 2 = O(n^2)。我们可以战略性地使用这个公式。

首先,正如@Tim 所提到的,我们可以只考虑不包含任何大于right 的数字的子列表的总和,方法是将列表划分为大于right 的数字。这将任务减少为仅考虑所有元素小于或等于right 的子列表,然后对答案求和。

接下来,通过根据大于或等于left 的数字对缩减的子列表进行分区,拆分缩减的子列表(是的,子列表的子列表)。对于这些子列表中的每一个,计算该子列表的子列表的可能子列表的数量(如果子列表的长度为k,则为k * (k + 1) / 2)。一旦对子列表的所有子列表完成此操作,将它们加在一起(将它们存储在,比如说,w)然后计算该子列表的可能子列表的数量并减去w

然后按总和汇总您的结果。

最坏情况:对于数组中的每个元素ee &lt; left

时间复杂度:O(n)

我知道这很难理解,所以我包含了工作代码:

def compute(sliced, lo, hi, left):
    num_invalid = 0
    start = 0
    search_for_start = True
    for end in range(lo, hi):
        if search_for_start and sliced[end] < left:
            start = end
            search_for_start = False
        elif not search_for_start and sliced[end] >= left:
            num_invalid += (end - start) * (end - start + 1) // 2
            search_for_start = True
    if not search_for_start:
        num_invalid += (hi - start) * (hi - start + 1) // 2
    return ((hi - lo) * (hi - lo + 1)) // 2 - num_invalid

def query(sliced, left, right):
    ans = 0
    start = 0
    search_for_start = True
    for end in range(len(sliced)):
        if search_for_start and sliced[end] <= right:
            start = end
            search_for_start = False
        elif not search_for_start and sliced[end] > right:
            ans += compute(sliced, start, end, left)
            search_for_start = True
    if not search_for_start:
        ans += compute(sliced, start, len(sliced), left)
    return ans

【讨论】:

  • 这绝对比我的方法快得多,但又像@Tim 解决方案一样,这不适用于大于 10**4 的查询
  • 这个解的空间复杂度是O(1)。这个解决方案实际上适用于任何大小的列表,只要列表可以放入内存。如果您在使用此解决方案时遇到 MemoryError,则意味着 Python 无法为传入的列表分配足够的空间。
  • @Demonking28 你能解释一下吗?我不明白你的意思。
  • 如果我将 10**5 个列表一一传递给任何长度为 N 的函数作为查询,那么执行所有此类查询所需的时间会很长,但您的解决方案仍然运行得更快比我的。
  • 感谢您首先解释您的方法背后的想法 :)
猜你喜欢
  • 2015-06-28
  • 1970-01-01
  • 1970-01-01
  • 2013-10-21
  • 2020-12-09
  • 1970-01-01
  • 2021-07-25
  • 2018-06-06
  • 2021-11-09
相关资源
最近更新 更多