https://app.codility.com/programmers/lessons/17-dynamic_programming/min_abs_sum/
https://codility.com/media/train/solution-min-abs-sum.pdf
For a given array A of N integers and a sequence S of N integers from the set {−1, 1}, we define val(A, S) as follows:
val(A, S) = |sum{ A[i]*S[i] for i = 0..N−1 }|
(Assume that the sum of zero elements equals zero.)
For a given array A, we are looking for such a sequence S that minimizes val(A,S).
Write a function:
class Solution { public int solution(int[] A); }
that, given an array A of N integers, computes the minimum value of val(A,S) from all possible values of val(A,S) for all possible sequences S of N integers from the set {−1, 1}.
For example, given array:
A[0] = 1 A[1] = 5 A[2] = 2 A[3] = -2
your function should return 0, since for S = [−1, 1, −1, 1], val(A, S) = 0, which is the minimum possible value.
Write an efficient algorithm for the following assumptions:
- N is an integer within the range [0..20,000];
- each element of array A is an integer within the range [−100..100].
思路:一个比较直观的想法是背包,复杂度N*sum(A),大概20000*20000*100,会TLE
# you can write to stdout for debugging purposes, e.g.
# print("this is a debug message")
def solution(A):
# write your code in Python 3.6
a=[abs(s) for s in A]
su=sum(a)
ta=su//2
dp=[[0 for _ in range(ta+1)] for _ in range(len(a))]
for i in range(a[0],ta+1): dp[0][i]=a[0]
for i in range(1,len(a)):
for j in range(ta+1):
dp[i][j]=dp[i-1][j]
if j>=a[i]:
dp[i][j]=max(dp[i-1][j], dp[i-1][j-a[i]]+a[i])
# print(su, dp[-1][-1])
return su-dp[-1][-1] - dp[-1][-1]
print(solution([1,5,2,2]))
print(solution([1,2,100]))
另外一个思路是考虑每来一个元素,update每个数能不能到达,复杂度仍然跟上面一样
这里dp[s]表示到目前为止(看到A[0..i]),s是否能到达
# you can write to stdout for debugging purposes, e.g.
# print("this is a debug message")
def solution(A):
# write your code in Python 3.6
a=[abs(s) for s in A]
su=sum(a)
dp=[False for _ in range(su//2+1)]
dp[0]=True
dp2=dp.copy()
for i in a:
for p in range(i,su//2+1):
dp2[p]=dp[p]|dp[p-i]
for p in range(i,su//2+1):
dp[p]=dp2[p]
for i in range(su//2,-1,-1):
if dp[i]: return su-2*i
print(solution([1,5,2,2]))
print(solution([1,5,2,-2]))
print(solution([1,2,100]))
但是注意到A数组的每个数范围比较小,所以有很多重复,可以先对A计数,然后用dp[s]表示:看到i的时候,到s位置时,还有dp[s]个i剩余。
这就相当于把数组A重新排列一下,相同的数排到一起,然后用上面的方法进行DP(因为排在一起,所以可以减小计算量)
# you can write to stdout for debugging purposes, e.g.
# print("this is a debug message")
from collections import Counter
def solution(A):
# write your code in Python 3.6
if len(A)==0: return 0
if len(A)==1: return abs(A[0])
a=[abs(s) for s in A]
su=sum(a)
ma=max(a)
d=Counter(a)
dp=[-1 for _ in range(su//2+1)]
dp[0]=0
for i in range(1,ma+1):
if i not in d: continue
for p in range(su//2+1):
if dp[p]>=0: dp[p]=d[i] # dp[p]可以到达,完全不需要使用A[i]
elif p>=i and dp[p-i]>0: dp[p]=dp[p-i]-1 # dp数组虽然经过很多赋值,但是dp[p-i]一定表示到p-i的时候,还剩下dp[p-i]个i
for i in range(su//2,-1,-1):
if dp[i]>=0: return su-2*i
print(solution([]))
print(solution([1,5,2,2]))
print(solution([1,5,2,-2]))
print(solution([1,2,100]))