数组分割 (python)

有一个无序,元素个数为2n的正整数数组,要求:如何能够把这个数组分割为元素个数为n的两个数组,并使得两个数组的和最接近?

分析和解法

题目的本质就是要用2n个整数中找出n个,使得它们的和尽可能地靠近所有整数地一半。

解法 1

一个最为直接地暴力解法是:

  1. 先将数组的所有元素进行排序a_{1}<a_{2}<...<a_{2n}
  2. 将它们划分成为两个子数组s_{1} = a_{1}<a_{3}<...<a_{2n-1} .and. s_{2} = a_{2}<a_{4}<...<a_{2n}.
  3. s_{1}s_{2}中找出一对数进行交换,使得sum(s_{1})sum(s_{2})直接的插值尽可能的小,直到找不到可对换的为止。

该算法的缺点是s_{1}s_{2}的值不一定是最优的,优点是 s_{1}s_{2} 元素的个数相差最小。

解法 2

假设2n个整数的和是SUM。那么从2n个整数中找到n个元素的和,有三种可能的情况:大于SUM/2,等于SUM/2,小于SUM/2。因为是二分,因此我们只需要考虑小于等于SUM/2的情况就可以了。

可以使用动态规划的方法解决这个问题:

  1. 可以将问题分为2n步,第k步的定义是前k个元素是前k个元素中任意i个元素的和,所有可能的取值集合为s_{k}.(这里只考虑小于等于SUM/2的情况)
  2. 然后将第k步拆成两个小步。即首先得到前k-1个元素。通过k-1个元素的集合s_{k-1}中所有的元素加nums[k]得到集合为s_{k}
  3. 通过集合中的元素可逆推出子数组中的元素。

改时间复杂度为O(2^N), 因为heap[i+1].append(heap[i][j]+nums[k-1])代码中需要实现的次数至多是2^(n-1)次。

import heapq
nums = [1,5,7,8,9,6,3,11,20,17]
heap = [[0]]+[[] for _ in range(len(nums)//2)]
# heap[i]表示从nums中取i个数所能产生的和的集合
n = len(nums)//2
for k in range(1,2*n+1):
    i_max = min(k-1,n-1)
    for i in range(i_max,-1,-1):
        for j in range(len(heap[i])):
            if heap[i][j]+nums[k-1]<=sum(nums)//2:
                heap[i+1].append(heap[i][j]+nums[k-1])
print(sum(nums)//2)
for i in range(len(heap)):
    heapq.heapify(heap[i])
    print(heap[i])

 解法 3

 根据解法2的方法,可以设计类似于背包子集问题的解法。背包子集问题也是动态规划的一种。

nums = [1,5,7,8,9,6,3,11,20,17]
n, sum = len(nums), sum(nums)
isOK = [[False]*(sum//2+1) for _ in range(n//2+1)]
isOK[0][0]=True
for k in range(1,n+1):
    for i in range(min(k,n//2),0,-1):
        for v in range(1,sum//2+1):
            if v >= nums[k-1] and isOK[i-1][v-nums[k-1]]:
                isOK[i][v] = True
for i in range(n//2+1):
    for j in range(sum//2+1):
        print(isOK[i][j],end=' ')

该算法的时间复杂度为O(N*N*sum)。

你可能感兴趣的:(python,算法)