求解分组问题(百度面试题)

题目描述:任意数分三组,使得每组的和尽量相等

思路分析:看完题目稍作分析的后,想不到用什么算法解决这个问题,于是思路如其他人一样。

先排序,初始化三个空数组,每次从给出的数组中拿一个最大值放到项数和最小的数组中。

python实现

from operator import add
from functools import reduce

def sep(n,l):
    #降序排序
    l = sorted(l,reverse=True)
    group = l[:n]
    for i in l[n:]:
        #往总和最小的分组里添加
        group[n-1] += i
        group = sorted(group,reverse=True)
    return group

 

这里比较简单的直接求分组的项数和,并没有求出分组的情况。

而上网查了一下发觉有第二种思路,先把任意数求和再除以3求得分组的平均数,然后排序并遍历所有数,逐个放入初始化的3个空数组中,

当第一个数组尽量靠近平均值,然后转而放进第二个数组,当第二个数组也尽量靠近平均数后,剩余所有数放进第三个数组。

Python实现

def sep2(n,l):
    group = []
    l = sorted(l,reverse=True)
    #求平均值
    ave = reduce(add,l) // n
    sum = 0
    #往分组里添加直至 总和大于等于平均值
    for i in l:
        #sum += i
        sum = ifAdd(ave,sum,i)
        if sum >= ave and len(group) < 3:
            group.append(sum)
            sum = 0
    if sum !=0:
        group.append(sum)
    return group
    
def ifAdd(ave,sum,n):
    if (sum+n > ave) and (sum+n-ave > ave-sum):
        return sum
    else:
        return sum+n

 

但是,当测试数据时候,发现这2种方法都并各自的缺陷。(当然我写得相对比较粗糙,或许改进一下会比较靠近,但是问题依然存在)

list1 = [3, 8, 20, 15, 60, 1, 32]
list2 = [16,9,7,6,5,5]
n = 3

测试结果如下

sep1
[60, 40, 39]
[60, 52, 27]

sep2
[18, 16, 14]
[16, 16, 16]

于是请教了董老师(董付国),他给出的思路如下。先分成几个子列表,然后动态调整各子列表的元素,从元素之和最大的子列表中拿出最小的元素放到元素之和最小的子列表中,

尽管不能保证每次都能成功,但是已经相当接近了。

import random

def numberSplit(lst, n,threshold):
    '''lst为原始列表,内含若干整数,n为拟分份数
       threshold为各子列表元素之和的最大差值'''
    length = len(lst)
    p = length // n
    #尽量把原来的lst列表中的数字等分成n份
    partitions = []
    for i in range(n-1):
        partitions.append(lst[i*p:i*p+p])
    else:
        partitions.append(lst[i*p+p:])

    print('初始分组结果:', partitions)
    
    #不停地调整各个子列表中的数字
    #直到n个子列表中数字之和尽量相等
    times = 0
    while times < 1000:
        times += 1
        maxLst = max(partitions, key=sum)
        minLst = min(partitions, key=sum)
        #把大的子列表中最小的元素调整到小的子列表中
        m = min(maxLst)
        i = [index for index, value in enumerate(maxLst) if value==m][0]
        minLst.insert(0, maxLst.pop(i))
        print('第{0}步处理结果:'.format(times), partitions)
        first = sum(partitions[0])
        for item in partitions[1:]:
            if abs(sum(item)-first) > threshold:
                break
        else:
            break
    else:
        print('很抱歉,我无能为力,只能给出这样一个结果了。')
    return partitions

lst = [random.randint(1, 100) for i in range(10)]
print(lst)
result = numberSplit(lst, 3, 10)
print('最终结果:', result)
#输出各组数字之和
print('各子列表元素之和:')
for item in result:
    print(sum(item))

 

如果有更加完美或者更加适合的解题思路,欢迎在留言区指点。

posted @ 2017-02-24 22:54  lateink  阅读(992)  评论(0编辑  收藏  举报