【问题标题】:Finding maximum sum of occurrences of one element in two attempts from a list从列表中查找两次尝试中一个元素出现的最大总和
【发布时间】:2018-05-21 23:10:13
【问题描述】:

最好用例子来解释。如果 python 列表是 -

[[0,1,2,0,4],
 [0,1,2,0,2],
 [1,0,0,0,1],
 [1,0,0,1,0]]

我想选择 两个子列表,这将产生出现的零的最大总和 - 其中总和将按如下方式计算

SUM = No. of zeros present in the first selected sub-list + No. of zeros present in the second selected sub-list which were not present in the first selected sub-list.

在这种情况下,答案是 5。(第一个或第二个子列表和最后一个子列表)。 (请注意,不要选择第三个子列表,因为它在第三个索引中存在零,这与我们必须选择的第一个/第二个子列表中的相同,并且它的总和为 4,如果我们考虑最后一个子列表)

如果我们要在大输入上应用哪种算法最合适?有没有比 N2 时间更好的方法来做到这一点?

【问题讨论】:

  • 这在我看来是 n^2。您需要将每个列表与(n-1) 其他列表进行比较。我会将每个列表转换为看起来像 [x==0 for x in sublst] 并在它们上映射 XOR,对结果求和。
  • 您能否尝试重新解释您的示例。我不太明白为什么第三个子列表失败了
  • @AdamSmith n!比n^2差很多,枚举对不考虑顺序是n(n-1)~n^2,即n以下的值的总和而不是它们的乘积
  • @JaredGoguen bleh,今天是星期一,谢谢指正:)
  • 你说“条件是它们不在同一个位置”。这到底是什么意思呢?假设输入是[[0, 1], [0, 0], [3, 4]]。预期的输出是什么? [0, 0] 与任何其他列表相结合,因为它在不同的地方有最多的零?或者[0, 0][0, 1] 结合起来根本不是有效的输出,因为它们共享一个 0 作为第一个元素?或者 [0, 0] + [0, 1] 一个有效的输出,但它只算 1 零?

标签: python algorithm list permutation


【解决方案1】:

二元运算对于这项任务非常有用:

  1. 将每个子列表转换为二进制数,其中一个0变成1位,其他数字变成0位。

    例如,[0,1,2,0,4] 将变成 10010,即 18。

  2. 消除重复数字。

  3. 将剩余的数字成对组合,然后用二进制 OR 组合它们。
  4. 找出 1 位最多的数。

代码:

lists = [[0,1,2,0,4],
         [0,1,2,0,2],
         [1,0,0,0,1],
         [1,0,0,1,0]]

import itertools

def to_binary(lst):
    num = ''.join('1' if n == 0 else '0' for n in lst)
    return int(num, 2)

def count_ones(num):
    return bin(num).count('1')

# Step 1 & 2: Convert to binary and remove duplicates
binary_numbers = {to_binary(lst) for lst in lists}

# Step 3: Create pairs
combinations = itertools.combinations(binary_numbers, 2)

# Step 4 & 5: Compute binary OR and count 1 digits
zeros = (count_ones(a | b) for a, b in combinations)

print(max(zeros))  # output: 5

【讨论】:

  • 就其价值而言,这里的二进制操作并没有什么特别之处。您可以将非零转换为一,然后将它们以 10 为底添加并计数。当数组以一个或多个零开头时,两者都会失败。
  • @Mark_M 如果两个数字在同一位置有 0,则加法会产生不正确的结果。这就是我使用二进制或的原因。但我没有看到数组中前导零的问题。你能详细说明一下吗?
  • 当数字在同一个位置有零时,它们加到零,这就是我们想要的——不要在两个数组中都计算零。所以: [1, 0, 1] + [1, 0, 0] 将是 201 - 数个数 = 1,这是正确的答案。
  • @Mark_M 这是XOR 的答案,而不是OR。 OP 似乎想要按位或 (([1, 0, 1], [1, 0, 0]) = 2)
  • 好的,现在我明白了——我没有看到 OP 的编辑说明了这一点。很抱歉造成混乱。
【解决方案2】:

朴素算法的效率是 O(n(n-1)*m) ~ O(n2m) 其中n是列表的数量,m是每个列表的长度.当 n 和 m 在大小上相当时,这相当于 O(n3)。

观察朴素矩阵乘法也是 O(n3) 可能会有所帮助。这可能会导致我们采用以下算法:

  1. 只用 1 和 0 编写每个列表,其中 1 表示非零条目。
  2. 将这些列表排列在矩阵 A 中。
  3. 计算乘积 M=AAT
  4. 找到M中的最小元素;行和列对应于产生最大数量的非重叠零的列表。

这里,(3)是算法的限制步骤。渐近地,根据您的矩阵乘法算法,您可以将复杂度降低到大约 O(n2.4)。

Python 实现示例如下所示:

import numpy as np

lists = [[0,1,2,0,4],
         [0,1,2,0,2],
         [1,0,0,0,1],
         [1,0,0,1,0]]

filtered = list(set(tuple(1 if e else 0 for e in sub) for sub in lists))
A = np.mat(filtered)
D = np.einsum('ik,jk->ij', A, A)

indices= np.unravel_index(np.argmin(D), D.shape)

print(f'{indices}: {len(lists[0]) - D[indices]}') # (0, 3): 0

请注意,该算法本身具有根本的低效率,即它同时计算点积矩阵的下三角和上三角两半。但是,numpy 的加速可能会从组合方法中抵消这一点。请看下面的计时结果:

def numpy_approach(lists):
    filtered = list(set(tuple(1 if e else 0 for e in sub) for sub in lists))
    A = np.mat(filtered, dtype=bool).astype(int)
    D = np.einsum('ik,jk->ij', A, A)
    return len(lists[0]) - D.min()

def itertools_approach(lists):
    binary_numbers = {int(''.join('1' if n == 0 else '0' for n in lst), 2) 
        for lst in lists}
    combinations = itertools.combinations(binary_numbers, 2)
    zeros = (bin(a | b).count('1') for a, b in combinations)
    return max(zeros)


from time import time

N = 1000
lists = [[random.randint(0, 5) for _ in range(10)] for _ in range(100)]

for name, function in {
        'numpy approach': numpy_approach, 
        'itertools approach': itertools_approach
        }.items():
    start = time()
    for _ in range(N):
        function(lists)
    print(f'{name}: {time() - start}')

# numpy approach: 0.2698099613189697
# itertools approach: 0.9693171977996826

【讨论】:

    【解决方案3】:

    算法应该看起来像(以 Haskell 代码为例,以免在 Python 中使过程变得微不足道:

    1. 将每个子列表变成“Is zero”或“Isn't zero”

      map (map (\x -> if x==0 then 1 else 0)) bigList 
      
    2. 枚举列表以便保留索引

      enumList = zip [0..] bigList
      
    3. 将每个子列表与其连续的子列表进行比较

      myCompare = concat . go
        where
        go []             = []
        go ((ix, xs):xss) = [((ix, iy), zipWith (.|.) xs ys) | (iy, ys) <- xss] : go xss
      
    4. 计算你的最大值

      best = maximumBy (compare `on` (sum . snd)) $ myCompare enumList
      
    5. 提取索引

      result = fst best
      

    【讨论】:

      猜你喜欢
      • 2011-02-15
      • 1970-01-01
      • 2018-05-21
      • 2018-05-08
      • 2021-08-29
      • 2020-01-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多