Выберите комбинацию элементов из массива, сумма которых является наименьшим возможным положительным числом

Предположим, у меня есть массив из M элементов, все числа, отрицательные, положительные или нулевые.

Может ли кто-нибудь предложить алгоритм выбора N элементов из массива, чтобы сумма этих N элементов была наименьшим возможным положительным числом?

Возьмите этот массив, например:

-1000,-700,-400,-200,-100,-50,10,100,300,600,800,1200

Теперь мне нужно выбрать любые 5 элементов так, чтобы их сумма была наименьшим возможным положительным числом.


person Shubham Gupta    schedule 27.06.2013    source источник
comment
не могли бы вы добавить больше информации? Каковы пределы для N, M и чисел?   -  person Ivaylo Strandjev    schedule 27.06.2013
comment
Обновил основной пост, спасибо :)   -  person Shubham Gupta    schedule 27.06.2013
comment
нет, вы не добавляли никаких ограничений. Мне нужно знать, каковы максимально допустимые значения N, M и чисел, чтобы я знал, какова необходимая вычислительная сложность решения.   -  person Ivaylo Strandjev    schedule 27.06.2013
comment
Немного, в пределах легкого вычислительного диапазона, например, N и M не могут превышать 10000. Это также на более высоком пределе.   -  person Shubham Gupta    schedule 27.06.2013
comment
Пожалуйста, уточните вопрос. Вы говорите, что числа могут быть выбраны случайным образом из массива или они должны быть непрерывным подмассивом. Каков верхний предел сложности алгоритма?   -  person Algorithmor    schedule 27.06.2013
comment
Их можно выбирать случайным образом, проверки сложности алгоритма на данный момент нет.   -  person Shubham Gupta    schedule 28.06.2013
comment
@Ash Я согласен, что это было бы намного интереснее, если бы существовал какой-то верхний предел сложности, или нам нужно было найти наиболее эффективный способ!   -  person TooTone    schedule 28.06.2013
comment
Вас интересуют алгоритмы с возможно неоптимальными результатами, но намного лучшей временной сложностью?   -  person Ondrej Bozek    schedule 28.06.2013
comment
будут ли элементы массива всегда различаться?   -  person גלעד ברקן    schedule 28.06.2013
comment
Похоже на проблему с суммой подмножеств.   -  person Bernhard Barker    schedule 28.06.2013
comment
(1) Нам нужна как можно более низкая сложность, эта полная логика должна быть реализована в финансовой системе при совершении платежей и группировании кредит-нот, т.е. отрицательных счетов-фактур и положительных счетов-фактур, сохранить максимальный номер счета-фактуры, чтобы наибольшее количество кредит-нот используются для оплаты, оставьте сумму положительной для окончательного платежа. Счета-фактуры могут достигать более 1 лака за один платеж. (2) Элементы массива не могут повторяться. (3) Да, я был бы доволен алгоритмом, который обеспечивает неоптимальное решение с лучшей временной сложностью.   -  person Shubham Gupta    schedule 28.06.2013
comment
Ну, я подумал, что нужно взять 2 массива и поместить плюсы в один и минусы в другой. Отсортируйте оба в порядке убывания в каждом массиве. Выберите первое положительное число, которое будет самым большим числом, и добавьте его к первому отрицательному числу, которое будет самым большим числом по абсолютной величине для отрицаний. Если сумма положительная, добавьте отрицательную, иначе положительную, повторите. В конце концов, может потребоваться удалить отрицательное число, потому что положительная сумма не может быть достигнута, мне нужно решение с лучшей временной сложностью, чем это!   -  person Shubham Gupta    schedule 28.06.2013
comment
Я обновил свое неоптимальное решение... проверьте его, если хотите... хотя формально оно может быть не таким оптимальным и эффективным, как целочисленное программирование.   -  person גלעד ברקן    schedule 29.06.2013
comment
@ShubhamGupta Я обновил свой ответ, чтобы использовать терминологию, соответствующую вашему вопросу.   -  person Timothy Shields    schedule 01.07.2013


Ответы (6)


Формулировка

Для i = 1, ..., M:

  • Пусть a_i будет i номером в вашем списке кандидатов
  • Пусть x_i обозначает, входит ли i число в ваш набор из N выбранных чисел.

Затем вы хотите решить следующую задачу целочисленного программирования.

minimize: sum(a_i * x_i)
with respect to: x_i
subject to:
    (1) sum(a_i * x_i) >= 0
    (2) sum(x_i) = N
    (3) x_i in {0, 1}

Вы можете применить к этой задаче целочисленный программный решатель «из коробки», чтобы найти оптимальное решение или субоптимальное решение с контролируемой точностью.

Ресурсы

person Timothy Shields    schedule 28.06.2013

Если вы хотите найти наилучшее возможное решение, вы можете просто использовать грубую силу, т.е. попробуйте все возможные комбинации пяти чисел.

Что-то вроде этого очень быстрого и грязного алгоритма:

public List<Integer> findLeastPositivSum(List<Integer> numbers) {
    List<Integer> result;
    Integer resultSum;
    List<Integer> subresult, subresult2, subresult3, subresult4, subresult5;
    for (int i = 0; i < numbers.size() - 4; i++) {
        subresult = new ArrayList<Integer>();
        subresult.add(numbers.get(i));
        for (int j = i + 1; j < numbers.size() - 3; j++) {
            subresult2 = new ArrayList<Integer>(subresult);
            subresult2.add(j);
            for (int k = j + 1; k < numbers.size() - 2; k++) {
                subresult3 = new ArrayList<Integer>(subresult2);
                subresult3.add(k);
                for (int l = k + 1; l < numbers.size() - 1; l++) {
                    subresult4 = new ArrayList<Integer>(subresult3);
                    subresult4.add(k);
                    for (int m = l + 1; m < numbers.size(); m++) {
                        subresult5 = new ArrayList<Integer>(subresult4);
                        subresult5.add(k);
                        Integer subresultSum = sum(subresult5);
                        if (subresultSum > 0) {
                            if (result == null || resultSum > subresultSum) {
                                result = subresult;
                            }
                        }
                    }
                }
            }
        }
    }
    return result;
}

public Integer sum(List<Integer> list) {
    Integer result = 0;
    for (Integer integer : list) {
        result += integer;
    }
    return result;
}

Это действительно быстрый и грязный алгоритм, его можно сделать более элегантно. Я могу предоставить более чистый алгоритм, например. используя рекурсию.

Это также может быть дополнительно оптимизировано. Например. вы можете удалить похожие числа из списка ввода в качестве первого шага.

person Ondrej Bozek    schedule 27.06.2013
comment
Звучит хорошо, но когда размер будет около 10К элементов, из 9К придется подобрать, это убьет время :) - person Shubham Gupta; 28.06.2013
comment
Конечно, это алгоритм грубой силы с экспоненциальной сложностью. Есть более быстрый алгоритм, но он, вероятно, даст неоптимальные результаты, но он будет намного быстрее. Вас интересуют алгоритмы с возможно неоптимальными результатами? - person Ondrej Bozek; 28.06.2013
comment
Да, я был бы доволен алгоритмом, который обеспечивает неоптимальное решение с лучшей временной сложностью. - person Shubham Gupta; 28.06.2013

Пусть исходный массив уже закорочен, или я думаю, что это будет работать, даже если он не закорочен..
N -> Длина массива
M -> Требуется элемент.
R[] -> Ответ
TEMP[] -> Для расчетов
minSum -> minSum
A[] -> Исходный ввод

Все вышеперечисленные переменные определены глобально

int find(int A[],int start,int left)
{
    if(left=0)
    {
        //sum elements in TEMP[] and save it as curSum
        if(curSum<minSum)
        {
        minSum=curSum;
        //assign elements from TEMP[] to R[] (i.e. our answer)      
        }
    }

    for(i=start;i<=(N-left);i++)
    {
        if(left==M)
            curSum=0;
        TEMP[left-1]=A[i];
        find(A[],i+1,left-1);
    }
}

// Сделал в спешке, так что может быть какая-то ошибка..

Рабочее решение для ideone: http://ideone.com/YN8PeW

person skbly7    schedule 27.06.2013
comment
Теперь доступно на ideone.com с соответствующей программой: ideone.com/YN8PeW... Ответ на тестовый пример из моя программа: 800 600 10 -400 -1000 :) Надеюсь, это сделает этот вопрос решенным наверняка .. - person skbly7; 27.06.2013
comment
Ну, это грубая сила, мне не поможет :) - person Shubham Gupta; 28.06.2013

Я полагаю, что алгоритм Кадане поможет, хотя он рассчитан на максимальную сумму но я также реализовал его, чтобы найти минимальную сумму, хотя сейчас не могу найти код.

person Rohit    schedule 27.06.2013
comment
Я так не думаю. Здесь вам может понадобиться сначала увеличить, а затем уменьшить накопленную сумму, - person Ivaylo Strandjev; 27.06.2013
comment
Правда, так наибольшую сумму нельзя превратить в наименьшую положительную сумму :) - person Shubham Gupta; 27.06.2013

Вот что-то неоптимальное в Haskell, которое (как и многие из моих идей), вероятно, можно было бы еще лучше оптимизировать. Это выглядит примерно так:

  1. Отсортируйте массив (я получил интересные результаты, попробовав как по возрастанию, так и по убыванию)
  2. B N = первые N элементов массива
  3. B (i), для i > N = лучший кандидат; где (предполагая целые числа), если они оба меньше 1, кандидаты сравниваются по модулю их сумм; если они оба равны 1 или больше, по их суммам; и если только один кандидат больше 0, то этот кандидат выбирается. Если сумма кандидата равна 1, верните этого кандидата в качестве ответа. Кандидатами являются:
    B (i-1), B (i-1)[2,3,4..N] ++ массив [i], B (i-1)[1,3,4 ..N] ++ массив [i]...B (i-1)[1,2..N-1] ++ массив [i]
    B (i-2)[2,3, 4..N] ++ массив [i], B (i-2)[1,3,4..N] ++ массив [i]...B (i-2)[1,2..N -1] ++ массив [i]
    ...
    B (N)[2,3,4..N] ++ массив [i], B (N)[1,3, 4..N] ++ массив [i]...B (N)[1,2..N-1] ++ массив [i]

Обратите внимание, что для той части массива, где числа отрицательные (в случае сортировки по возрастанию) или положительные (в случае сортировки по убыванию), шаг 3 можно выполнить сразу без вычислений.

Вывод:

*Main> least 5 "desc" [-1000,-700,-400,-200,-100,-50,10,100,300,600,800,1200]
(10,[-1000,600,300,100,10])
(0.02 secs, 1106836 bytes)

*Main> least 5 "asc" [-1000,-700,-400,-200,-100,-50,10,100,300,600,800,1200]
(50,[300,100,-200,-100,-50])
(0.02 secs, 1097492 bytes)

*Main> main -- 10000 random numbers ranging from -100000 to 100000
(1,[-106,4,-40,74,69])
(1.77 secs, 108964888 bytes)

Код:

import Data.Map (fromList, insert, (!))
import Data.List (minimumBy,tails,sort)
import Control.Monad.Random hiding (fromList)

array = [-1000,-700,-400,-200,-100,-50,10,100,300,600,800,1200]

least n rev arr = comb (fromList listStart) [fst (last listStart) + 1..m]
 where
  m = length arr
  r = if rev == "asc" then False else True
  sorted = (if r then reverse else id) (sort arr)
  listStart = if null lStart 
                 then [(n,(sum $ take n sorted,take n sorted))] 
                 else lStart
  lStart = zip [n..] 
         . takeWhile (all (if r then (>0) else (<0)) . snd) 
         . foldr (\a b -> let c = take n (drop a sorted) in (sum c,c) : b) [] 
         $ [0..]
  s = fromList (zip [1..] sorted)
  comb list [] = list ! m
  comb list (i:is)
    | fst (list ! (i-1)) == 1 = list ! (i-1)
    | otherwise              = comb updatedMap is 
   where updatedMap = insert i bestCandidate list 
         bestCandidate = comb' (list!(i - 1)) [i - 1,i - 2..n] where
           comb' best []     = best
           comb' best (j:js)
             | fst best == 1 = best
             | otherwise     =    
                 let s' = map (\x -> (sum x,x))
                        . (take n . map (take (n - 1)) . tails . cycle) 
                        $ snd (list!j)
                     t = s!i
                     candidate = minimumBy compare' (map (add t) s')
                 in comb' (minimumBy compare' [candidate,best]) js
  add x y@(a,b) = (x + a,x:b)
  compare' a@(a',_) b@(b',_) 
    | a' < 1    = if b' < 1 then compare (abs a') (abs b') else GT
    | otherwise = if b' < 1 then LT else compare a' b'

rnd :: (RandomGen g) => Rand g Int
rnd = getRandomR (-100000,100000)

main = do
  values <- evalRandIO (sequence (replicate (10000) rnd))
  putStrLn (show $ least 5 "desc" values)
person Community    schedule 28.06.2013

Предположение: M - исходный массив

песудокод

 S = sort(M);
 R = [];
 sum = 0;
 for(i=0, i < length(S); i++){
   sum = sum + S[i];
   if(sum < 1){
     R.push(S[i]);
   }else{
     return R;
   }
 }
person Chetan    schedule 27.06.2013
comment
Нет, не сработает. Проверьте массив здесь: (-1000,-700,-400,-200,-100,-50,10,100,300,600,800,1200) Теперь мне нужно только 5 элементов, чтобы сумма была наименьшей положительной. Вы не принимаете во внимание ограничение, что количество элементов, которые нужно подобрать, должно быть только 5! - person Shubham Gupta; 27.06.2013