Sum of a subset of numbers

2019-08-09 01:38发布

问题:

Say I have one number 'n' and a table of numbers. I want to choose up to four of the numbers in the table, and the sum of those four will be the closest possible match to n. Given length 'L' of the table, the number of combinations it has to go through is (6*L + 11*L^2 + 6*L^3 + L^4)/24.

ex. Say I have the variable

n = 100

and the set of numbers

t = {86, 23, 19, 8, 42, 12, 49}

Given this list, the closest combination of four to n is 49 + 23 + 19 + 8 = 99.

What is the optimal way of doing this with the least possible number of calculations?

回答1:

This looks like a variation of the 'subset sum' (see: http://en.wikipedia.org/wiki/Subset_sum_problem) problem which is known to to be NP complete, so unfortunately most probably there won't be any clever algorithm at all that in the worst-case will run any faster that exponential in the number of items.

In case there are not many items to check (something about 10 or so) you might try a depth first search pruning branches as soon as possible.

If there are a lot more items to check most probably instead of searching for the optimal solution you might better try to find a somewhat good approximation.



回答2:

Assuming all numbers are positive integers, it could be done as Yexo pointed out:

local n = 100
local t = {86, 23, 19, 8, 42, 12, 49}
local max_terms = 4
-- best[subset_size][terms][k] = {abs_diff, expr}
local best = {[0] = {}}
for k = 1, n do best[0][k] = {k, ''} end
for terms = 0, max_terms do best[terms] = best[0] end
for subset_size = 1, #t do
   local new_best = {}
   for terms = subset_size == #t and max_terms or 0, max_terms do
      new_best[terms] = {}
      for k = subset_size == #t and n or 1, n do
         local b0 = best[terms][k]
         local diff = k - t[subset_size]
         local b1 = terms > 0 and (
            diff > 0 and {
               best[terms-1][diff][1],
               best[terms-1][diff][2]..'+'..t[subset_size]
            } or {math.abs(diff), t[subset_size]}
         ) or b0
         new_best[terms][k] = b1[1] < b0[1] and b1 or b0
      end
   end
   best = new_best
end
local expr = best[max_terms][n][2]:match'^%+?(.*)'
print((loadstring or load)('return '..expr)()..' = '..expr)

-- Output
99 = 23+19+8+49