【问题标题】:Maximum combined difference in array, Python数组中的最大组合差异,Python
【发布时间】:2021-04-11 08:07:57
【问题描述】:

问题

我得到了一个n <= 100 正整数序列。 如何找到通过重新排列数组中的元素可以达到的元素之间的最大可能组合差异?

例如,对于序列 {8, 4, 1, 2, 3},起始差为 (8-4) + (4-1) + (2-1) + (3-2) = 9,但对于 {4, 8, 1, 3, 2} 可以达到 14。

最好的排列是{4, 1, 8, 2, 3}。预期的答案将是可以达到的最大值,因此在本例中为 17。

我的方法

  • 我会对数组进行排序,然后创建一个 Python collections.deque() 以交替附加到两侧的最小和最大元素;这有意义吗?

  • 这种幼稚的方法不正确吗?

  • 我们可以尝试两种情况,从最小元素和最大元素开始。

排列数可以达到100!所以没有野蛮人会这样做。

一些代码



input_data = [int(x) for x in input().split(" ")]
input_data.sort()


def maximum_difference_sort(data: list, *, start_with_max):
    queue = deque()
    indices = [0, -1] if start_with_max else [-1, 0]
    queue.append(data.pop(indices[1]))
    while True:
        try:
            queue.append(data.pop(indices[0]))
            queue.appendleft(data.pop(indices[0]))

            queue.append(data.pop(indices[1]))
            queue.appendleft(data.pop(indices[1]))
        except IndexError:
            break
    return list(queue)


def combined_difference(seq):
    s = 0
    for i in range(1, len(seq)):
        s += abs(seq[i] - seq[i-1])
    return s


print(combined_difference(maximum_difference_sort(input_data, start_with_max=True)))```

【问题讨论】:

  • @FedericoBaù 很明显,这是一个操作方法问题,并且没有源代码......(对不起,如果这看起来很粗鲁,但你读过这个问题了吗?)
  • 虽然这类问题在 SO 上只有一半的人接受。
  • @Kangaroo976 你的问题是从哪里来的? (作业?)
  • 这不是作业哈哈,至少不是我的。只是我在网上某处发现的一个有趣的问题。我最近一直在考虑它,但我真的没有看到任何可以保证有效的好的解决方案。
  • 这是声明的证明:math.stackexchange.com/a/3975296/177399

标签: python algorithm sorting optimization max


【解决方案1】:

分析蛮力结果,我确定了一些我认为可以用来快速计算结果的价值顺序模式:

  • 中间值总是第一个(或镜像顺序中的最后一个)
  • 对于奇数大小的列表,剩余元素的中间值总是最后一个
  • 剩余值(不包括第一个和奇数的最后一个)在排序元素的上半部分和下半部分之间交替,以上半部分或下半部分开头
  • 上半部和下半部按升序或降序排列

根据这一观察(推测),该函数最多需要检查 8 个模式:

def sumDiffs(R): return sum(abs(a-b) for a,b in zip(R,R[1:]))

def maxDiffs(A):
    if len(A)<=2 : return sumDiffs(A),A
    S = sorted(A)
    first  = [S.pop(len(S)//2)]
    half   = len(S)//2
    lower  = S[:half]
    upper  = S[-half:]
    last   = S[half:-half]
    def interlace(L,R): return [n for ab in zip(L,R) for n in ab]
    patterns = []
    for L,R in [(lower,upper),(upper,lower)]:
        for ld,rd in [(1,1),(1,-1),(-1,1),(-1,-1)]:
            patterns.append(first + interlace(L[::ld],R[::rd]) + last)
    return max( (sumDiffs(p),p) for p in patterns ) 
                
print(*maxDiffs([8, 4, 1, 2, 3]))     # 17 [3, 1, 8, 2, 4]
print(*maxDiffs([8, 4, 1, 2, 3, 7]))  # 25 [4, 1, 8, 2, 7, 3]

为了验证这一点,我在随机生成的各种大小的列表上将结果与蛮力函数进行了比较:

from itertools import permutations
def bruteForce(A):
    return max( (sumDiffs(P),P) for P in permutations(A) )

import random
for size in range(1,101):
    failedCount = 0
    for i in  range(10):
        A = list(random.randrange(size*size) for _ in range(size))
        bfSum,bfValues = bruteForce(A)
        maxSum,values  = maxDiffs(A)
        if maxSum!=bfSum:
           print(size,"failed:",bfSum,maxSum,bfValues,values)
           failedCount += 1
    print("size",size,"failed:",failedCount)

size 1 failed: 0
size 2 failed: 0
size 3 failed: 0
size 4 failed: 0
size 5 failed: 0
size 6 failed: 0
size 7 failed: 0
size 8 failed: 0
size 9 failed: 0
size 10 failed: 0
size 11 failed: 0

虽然我没有正式的证明,但这似乎有效(而且速度非常快)。

【讨论】:

  • 比我之前提出的更快吗?我的解决方案只检查一种情况。
  • 可能不会比你的快。我没有测量执行时间
  • @Kangaroo976,我测试了执行时间,你的函数大约快了 3 倍。但是,它有时会给出错误的答案。例如: [11, 9, 1, 16, 8] 打印 34 而不是 35 (9, 16, 1, 11, 8)。其他示例: [9, 7, 19, 24, 3] --> 56 而不是 64 (9, 24, 3, 19, 7), [2, 9, 20, 1, 19] --> 62 而不是65(9、20、1、19、2)
  • 所以我们至少需要检查两种情况,从最大开始和从最小开始
【解决方案2】:

我处理问题的方式略有不同。

以这个列表为例:

[4, 1, 8, 2, 7, 3, 2, 4, 10, 3]

到目前为止,我可以看到给出的其他解决方案以及您的@Kangaroo976,列表以这种方式排序:

[4, 3, 7, 2, 10, 1, 8, 2, 4, 3]
[['LEFT'], ['LEFT'], ['LEFT'], ['LEFT'], ['MAX VAL'], ['RIGHT'], ['RIGHT'], ['RIGHT'], ['RIGHT'], ['RIGHT']]

相反,我以不同的方式进行描绘,所以基本上有 2 个 管道,它们分别是具有最大值和最小值的 2 个列表,因此与我的实现相同的输入列表,我会将列表排序为 2像这样:

# MAX       # MIN
|| 10   *   1 ||
|| 8   *    2 ||
|| 7   *    2 ||
|| 4   *    3 ||
|| 4   *    3 ||

然后您为每个数字计算每个 2 索引 2 次,因此您有如下组或 集群

# MAX       # MIN
|| 10   *   1 ||   (10 -1) + (10-2) + (8-1) + (8-2)
|| 8   *    2 ||

|| 7   *    2 ||   (7-2) + (7-3) + (4-2) + (4-3)
|| 4   *    3 ||

|| 4   *    3 ||  (4-3)

如果列表中有 odds 数字,例如 [4, 1, 8, 2, 7, 3, 2, 4, 10],则计算会略有不同:

# MAX       # MIN
|| 10   *   1 ||   (10 -1) + (10-2) + (8-1) + (8-2)
|| 8   *    2 ||

|| 7   *    2 ||   (7-2) + (7-3) + (4-2) + (4-3)
|| 4   *    3 ||

||    *    4  ||   # Not Counted

第一个解决方案

注意,比原来的慢

def algorithm(input_list):

    max_pipe = list()
    min_pipe = list()
    max_difference = 0


    while len(input_list) > 1:
        # ---------------  Left Side Max Pipe
        max_n = input_list.index(max(input_list))
        max_pipe.append(input_list.pop(max_n))

        # --------------- Right Side Min Pipe
        min_n = input_list.index(min(input_list))
        min_pipe.append(input_list.pop(min_n))
    if input_list:
        min_pipe.append(input_list.pop())
    max_comparison = len(min_pipe) - 1
    counter = 0  # NOTE: Counter reset each 4. Then adds +2 to offset
    offset = 0


    for max_val in range(max_comparison):

        # --------------- Handle Clusters of calculations, which are each 4 steps
        if counter == 4:
            counter = 0
            offset += 2

        for comparison in range(2):
            diff = max_pipe[max_val] - min_pipe[comparison + offset]
            max_difference += diff
            counter += 1
    if len(min_pipe) % len(max_pipe) == 0:  # NOTE: In list is uneven, needs a last comparison with both last values
        max_difference += max_pipe[-1] - min_pipe[-1]

    return max_difference

第二种解决方案

因此遵循这个逻辑,并在之前对列表进行排序并以Divide-and-conquer 方式划分列表,我想出了这个解决方案:

def algorithm(input_list):

    max_difference = 0
    # --------------- List Sorting & Setting Up Max And Min Pip
    input_list.sort()
    half = len(input_list)//2
    max_pipe = input_list[half:]
    min_pipe = input_list[:half]
    # --------------- Loop Utils
    total_comparison = len(input_list) - 2
    counter = 1
    offset = 0

    # --------------- Algorithm
    for n in range(half//2):
        if counter == total_comparison:  # The Algorithm can have only a certain and precise amount of calculations
            break
        comparison = 0
        for N in max_pipe[-counter::-1]:

            max_difference += N - min_pipe[offset*2]
            max_difference += N - min_pipe[offset*2 + 1]
            comparison += 1
            if comparison == 2:  # NOTE: Only 2 Comparison for each couple (Max, Min) num
                break
        counter += 2
        offset += 1

    max_difference += max_pipe[0] - min_pipe[-1]  # Last calculation left over
    return max_difference

基准测试

original = min(timeit.Timer(original_).repeat(1, 20))
print(mine)
print(original)
print('==='*15)
print(min(mine, original))

输出

5.1045565999999996  # Mine
5.519946699999999   # Kangaroo976
=============================================
5.1045565999999996

【讨论】:

    【解决方案3】:

    这似乎是个好主意。您希望最大化每个差异,因此您将从最高的开始,而不是通过放置两个最低的元素而不是最高的元素来最大化该差异,依此类推。当我想到这个问题时,这似乎是最好的主意。即使我认为我们不需要为 100 个元素出列。我会试试看!

    【讨论】:

      【解决方案4】:

      ? 我意识到我误读了您的问题并看到您已经构思了我在下面描述的方法(即交错最大和最小值,尝试将最小值和最大值都作为最里面的值)。所以无论如何,我只剩下我的实现和用于测试蛮力的代码。测试代码表明,这种“解决方案”基本上适用于所有情况,并且比蛮力方法快得多(但仍有证明问题)。

      我的代码:

      def little_big_sort(l):
          s = sorted(l)
          result = []
          result.append(s.pop(0))
      
          while len(s):
              if s:
                  result.insert(len(result), s.pop(-1))
              if s:
                  result.insert(0, s.pop(-1))
              if s:
                  result.insert(len(result), s.pop(0))
              if s:
                  result.insert(0, s.pop(0))
      
          return result
      
      def big_little_sort(l):
          s = sorted(l)
          result = []
          result.append(s.pop())
      
          while len(s):
              if s:
                  result.insert(len(result), s.pop(0))
              if s:
                  result.insert(0, s.pop(0))
              if s:
                  result.insert(len(result), s.pop(-1))
              if s:
                  result.insert(0, s.pop(-1))
      
          return result
      
      def diff_sum(l):
          result = 0
          for i in range(len(l[:-1])):
              result += abs(l[i] - l[i+1])
          return result
      
      
      def solution(l):
          return max(diff_sum(little_big_sort(l)), diff_sum(big_little_sort(l)))
      

      蛮力方法:

      def brute_force(l):
          result = 0
      
          for i in itertools.permutations(l):
              if diff_sum(i) > result:
                  result = diff_sum(i)
      
          return result
      

      对于测试:

      import random
      
      n = 2 # items in list
      m = 10000 # repetitions
      lo = 0
      hi = 100
      
      cases = [random.sample(range(lo, hi),n) for i in range(m)]
      
      for i in cases:
          assert brute_force(i) == solution(i), f'failure for case {i}'
      

      【讨论】:

        【解决方案5】:

        由于您交替使用较大和较小的数字,因此在每个交替位置您将在两个较大的数字之间有一个较小的数字。像bignum smallnum bignum smallnum.... 这样的东西。您可以轻松地排列较大的数字,从中间的最大数字开始,然后较小的数字更宽。
        现在,所有较小数字的排列都会给出相同的结果。

        假设当前的数字排列是a b c d e,其中数字按升序排列是b &lt; d &lt; e &lt; a &lt; c

        1. 当前差异:(a-b) + (c-b) + (c-d) + (e-d) = a+e+(2*c)-(2*b)-(2*d)
        2. 让我们交换bd。现在序列变为a d c b e。新差异 = (a-d) + (c-d) + (c-b) + (e-b) = a+e+(2*c)-(2*b)-(2*d),与之前的差异相同。
        3. 尝试将ae 交换,也会得到相同的结果。

        排列较大的数字是重要的部分,将较大的数字放在中间,因为它们对总差异的贡献是两倍。之后,可以按任何顺序插入较小的数字,因为这不会影响总差。

        【讨论】:

          猜你喜欢
          • 2023-03-18
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-11-17
          • 2021-12-24
          • 2019-04-08
          • 2019-06-12
          • 1970-01-01
          相关资源
          最近更新 更多