如何计算列表的最小不公平总和

6
我尝试总结一下问题陈述,大致如下:
给定n、k以及一个数组(列表)arr,其中n = len(arr),k是集合(1, n]中的整数。
对于一个数组(或列表)myList,不公平度之和定义为myList中所有可能配对(每个组合包含两个元素)之间的绝对差值之和。
为了解释清楚,如果mylist = [1, 2, 5, 5, 6],那么最小不公平度之和(MUS)是指上述定义下的最小值。请注意,元素按其在列表中的索引而非值来唯一确定。
MUS = |1-2| + |1-5| + |1-5| + |1-6| + |2-5| + |2-5| + |2-6| + |5-5| + |5-6| + |5-6|

如果您需要查看问题陈述,请单击此处我的目标 给定 n, k, arr(如上所述),找到所有可能的子数组的不公平和中的最小不公平和,并约束每个子数组的长度为 k [我相信这是一件好事,可以使我们的生活变得容易 :) ] 我尝试过的方法 嗯,这里还有很多要添加的,所以我会尽量简短。 我的第一种方法 是使用 itertools.combinations 获取所有可能的组合,使用 statistics.variance 检查其 数据分布(是的,我知道我有点乱)。 在查看下面的代码之前,您是否认为这些方差和不公平和是完全相关的(我知道它们强烈相关),即具有最小方差的子数组必须是具有 MUS 的子数组??
您只需要检查 LetMeDoIt(n,k,arr) 函数。如果需要MCVE,请查看下面的第二个代码片段。
from itertools import combinations as cmb
from statistics import variance as varn

def LetMeDoIt(n, k, arr):
    v = []
    s = []
    subs = [list(x) for x in list(cmb(arr, k))]  # getting all sub arrays from arr in a list

    i = 0
    for sub in subs:
        if i != 0:
            var = varn(sub)  # the variance thingy
            if float(var) < float(min(v)):
                v.remove(v[0])
                v.append(var)
                s.remove(s[0])
                s.append(sub)
            else:
                pass

        elif i == 0:
            var = varn(sub)
            v.append(var)
            s.append(sub)
            i = 1

    final = []
    f = list(cmb(s[0], 2))  # getting list of all pairs (after determining sub array with least MUS)
    
    for r in f:
        final.append(abs(r[0]-r[1]))  # calculating the MUS in my messy way

    return sum(final)

上述代码对于 n<30 运行正常,但对于更大的数字会出现 MemoryError 。 在 Python 聊天中,Kevin 建议我尝试使用 generator ,它非常 memory efficient (确实如此),但由于生成器会在我们 迭代它们时即时生成这些组合,因此估计 n=50, k=8 会需要超过 140 小时(:/)。
我在 Stack Overflow 上发布了同样的问题,链接在这里(您可能想要查看以正确理解我的意思 - 它包含讨论和 fusion 的答案,这使我想到了第二种方法 - 更好的方法(我应该说是 fusion 的方法 xD)。)。
第二种方法:
from itertools import combinations as cmb

def myvar(arr):   # a function to calculate variance
    l = len(arr)
    m = sum(arr)/l
    return sum((i-m)**2 for i in arr)/l

def LetMeDoIt(n, k, arr):
    sorted_list = sorted(arr)  # i think sorting the array makes it easy to get the sub array with MUS quickly
    variance = None
    min_variance_sub = None
    
    for i in range(n - k + 1):
        sub = sorted_list[i:i+k]
        var = myvar(sub)
        if variance is None or var<variance:
            variance = var
            min_variance_sub=sub
            
    final = []
    f = list(cmb(min_variance_sub, 2))  # again getting all possible pairs in my messy way

    for r in f:
        final.append(abs(r[0] - r[1]))

    return sum(final)

def MainApp():
    n = int(input())
    k = int(input())

    arr = list(int(input()) for _ in range(n))

    result = LetMeDoIt(n, k, arr)

    print(result)    

if __name__ == '__main__':
    MainApp()

这段代码对于 n 小于等于1000的情况(可能更大),是完美的,但是当 n 超过10000时(最大测试用例有 n=100000),由于 超时 而无法继续执行(在线评测限制为5秒 :/)。

=====

你会如何解决这个问题,以满足给定时间限制(5秒)下的所有测试用例?(问题在算法动态规划中列出)

(您可以参考以下内容:

  1. 其他候选人的成功提交记录(使用py3、py2、C++、java)- 以便您为我和未来的访问者解释那种方法)
  2. 问题设置者的一篇文章,解释如何解决该问题
  3. 问题设置者自己的解决方案代码(使用py2、C++)。
  4. 输入数据(测试用例)和期望输出

编辑1 ::

对于这个问题的未来访问者,我现在得出的结论是:
方差不公平总和并不是完全相关(它们是强烈相关),这意味着在许多整数列表中,具有最小方差的列表不一定总是具有最小不公平总和的列表。如果您想知道原因,我实际上在math stack exchange上单独提出了一个问题HERE,其中一位数学家为我证明了这一点 xD(这值得一看,因为它出人意料)。

至于整个问题,您可以阅读下面archer和Attersson的答案(仍在试图找出一种简单方法来解决此问题 - 目前应该不远了)


感谢您的任何帮助或建议 :)


4
对于这个(有趣的)问题,我心情复杂,因为这是一项 Hackerrank 挑战活动,而在 StackOverflow 上寻求帮助会破坏挑战活动的意义...... - Attersson
2
如果我的列表是[1, 2, 5, 5, 6],那么[...] MUS = |1-2| + |1-5| + |1-5| + |1-6| + |2-5| + |2-5| + |2-6| + |5-5| + |5-5| "你确定这里末尾没有漏掉 + |5-6| + |5-6| 吗? - Stef
1
@Stef 噢,那是个打错字 :/ 感谢你指出来 :)。已经修改了。 - P S Solanki
1
请注意,“subarray”被认为是数组的连续部分。你是不是想说“subset”呢? - גלעד ברקן
1
@PSSolanki 我看到你之前在聊天室里。等你回来的时候给我发个消息,我们可以在这里不打扰其他人的情况下讨论。 - MattDMo
显示剩余5条评论
2个回答

2
您必须按照排序后的列表进行工作,并仅检查具有连续元素的子列表。这是因为默认情况下,包括至少一个非连续元素的子列表将具有更高的不公平总和。
例如,如果列表为[1,3,7,10,20,35,100,250,2000,5000],并且您想要检查长度为3的子列表,则解决方案必须是[1,3,7] [3,7,10] [7,10,20]等中的一个,任何其他子列表(例如[1,3,10])将具有更高的不公平总和,因为10>7,因此它与其余元素的所有差异都将大于7。对于[1,7,10](左侧不连续)同样如此,因为1<3。
鉴于此,您只需检查长度为k的连续子列表,这将显着减少执行时间。
关于编码,以下代码应该可以工作:
def myvar(array):
    return sum([abs(i[0]-i[1]) for i in itertools.combinations(array,2)])  
  
def minsum(n, k, arr):
        res=1000000000000000000000 #alternatively make it equal with first subarray
        for i in range(n-k):
            res=min(res, myvar(l[i:i+k]))
        return res
    

关于您提出的代码,开始时定义变量res - 这是否与动态规划有关(我知道是什么,但不知道如何实现 xD)?您对方差与MUS之间的关系持什么立场? - P S Solanki
1
res是最小子集的初始值。我们必须在初始时设置一个非常大的值,它将逐渐被循环内的较低值所替换。当循环结束时,res将是我们正在寻找的最小不公平总和。如果最终的res可能非常大,而不是1000000000000,您可以最初将res=myvar(l[0:k])设置为始终返回正确结果。 - IoaTzimas
我明白了。实际上,我只会设置一个随机值,至少可以节省几个宝贵的微秒 ;)(如果我的理解是正确的)。 - P S Solanki
1
我在我的代码中添加了一个myvar函数。请检查整个代码是否正常工作。 - IoaTzimas
它能工作,但是当n<=10000时会因超时而终止。我尝试的确切代码是这个:我的代码...请看一下。如果您感兴趣,可以查看三个不同的成功提交此处 - 以便向我和未来的访问者解释他们的方法,或者基于它们建议其他方法。只是提醒,这个动态规划问题的时间复杂度应该是n log n - P S Solanki

1
我看到这个问题仍然没有完整的答案。我将写一个正确算法的轨迹,它将通过评判。为了尊重Hackerrank挑战的目的,我不会写代码。因为我们有工作解决方案。
1. 原始数组必须排序。这具有O(NlogN)的复杂度。 2. 在这一点上,您可以检查连续的子数组,因为非连续的子数组将导致更差(或相等,但不是更好)的“不公平总和”。这也在archer的答案中解释了。 3. 最后一个检查通道,查找最小的“不公平总和”可以在O(N)中完成。您需要为每个连续的k长子数组计算US。错误是对于每个步骤重新计算这个值,这个步骤需要O(k),这将使这个通道的复杂度达到O(k*N)。可以像您发布的编辑一样使用数学公式,在第1步之后初始化累积数组(用O(N)完成,空间复杂度也为O(N)),以O(1)的速度完成此操作。 “它起作用,但由于n<=10000而终止超时。” (来自archer问题的评论)
为了解释第三步,假设k = 100。当你滚动N长的数组并进行第一次迭代时,你必须像往常一样计算从元素0到99的子数组的US,需要100个通道。下一步需要你计算一个子数组的US,该子数组仅与前一个不同一个元素1到100,然后是2到101等等。如果有帮助的话,可以将其视为蛇。一个块被移除,一个块被添加。没有必要执行整个O(k)滚动。只需按照编辑说明中所述的数学方法来计算,您就可以在O(1)内完成它。
因为第一次排序,最终复杂度渐近地为O(NlogN)。

1
没问题 :-) 绝对不会有任何负面评价或其他事情。我仍然需要明确范围并提供帮助 :) (并查看问题得分,这非常好) - Attersson
1
哈哈哈,你的立场完全正确 :) 无论如何,我实际上在我的问题中添加了一部分“方差与最小不公平和”在“math.stackexchange” 这里,令我惊讶的是,我得到了一个意外的答案。 - P S Solanki
1
非常有趣且写得很好!使用方差的想法不幸地行不通。但是另一方面,通过应用“蛇形”优化,计算不公平总和没有问题。 - Attersson
1
@Atterson 看起来我不知怎么地创建了一个聊天室(SO 强制我这样做,我发誓 ;p)。实际上,我尝试实现了你提到的方法,我完全理解了“蛇”的比喻,我明白了为什么之前的方法会出现超时错误,我也明白了如何继续解决算法问题(感谢你解释的答案 - 我现在理解了动态规划的基本原理以减少时间复杂度)。我正在努力理解编辑中解释的数学部分,只有整个过程中的一步我似乎无法理解。 - P S Solanki
1
请查看作者的解决方案,您可以找到一个更好的公式来实现“逆向工程”和理解。https://paste.atilla.org/paste/31PSXK 的第31行。请注意:lis是数组,sum_lis是数组的累加和。还请注意在第25~27行中他如何计算第一个子数组的不公平值总和。 - Attersson
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接