Найти возможные числа в массиве, которые могут суммироваться до целевого значения - PullRequest
0 голосов
/ 09 февраля 2019

Учитывая, что у меня есть массив чисел, например [14,6,10] - Как я могу найти возможные комбинации / пары, которые могут добавить к заданному целевому значению.

например, у меня есть [14,6,10] , я ищу целевое значение 40 мой ожидаемый результат будет

 10 + 10 + 6 + 14
 14 + 14 + 6 + 6
 10 + 10 + 10 + 10

* Порядок не важен

Учитывая сказанное, это то, что я пробовал до сих пор:

function Sum(numbers, target, partial) {
  var s, n, remaining;

  partial = partial || [];

  s = partial.reduce(function (a, b) {
    return a + b;
  }, 0);

  if (s === target) {
     console.log("%s", partial.join("+"))
  }


  for (var i = 0; i < numbers.length; i++) {
    n = numbers[i];
    remaining = numbers.slice(i + 1);
    Sum(remaining, target, partial.concat([n]));
  }
}

>>> Sum([14,6,10],40);
// returns nothing

>>> Sum([14,6,10],24);
// return 14+10

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

Так каксделать это?

Ответы [ 2 ]

0 голосов
/ 27 марта 2019

TL & DR: перейдите ко второй части для реальной вещи

Часть I

@ Нина Шольц ответ на эту фундаментальную проблему просто показывает нампрекрасное проявление алгоритма.Честно говоря, это сильно смутило меня по двум причинам:

  1. Когда я пытаюсь [14,6,10,7,3] с целью 500, он совершает 36 783 575 вызовов функции iter без потери стека вызовов.Тем не менее, память не показывает значительного использования вообще.
  2. Мое решение для динамического программирования работает немного быстрее (или может быть нет), но нет никакого способа, как это было бы вышеописанным, без увеличения памяти на 16 ГБ.

Итак, я отложил свое решение и вместо этого начал исследовать ее код немного дальше на инструментах разработчика и обнаружил, как это красиво, так и немного его недостатков.

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

Проблема в том, что, будучи очень эффективной, она по-прежнему выполняет огромное количество избыточных вызовов.Таким образом, с небольшой модификацией 36 783 575 вызовов на iter могут быть сокращены до 20 254 744 ... примерно до 45%, что дает гораздо более быстрый код.Дело в том, что входной массив должен быть отсортирован по возрастанию.

Итак, вот и модифицированная версия алгоритма Нины.(Наберитесь терпения ... это займет около 25 секунд, чтобы завершить)

function getSum(array, sum) {
    function iter(index, temp) {cnt++ // counting iter calls -- remove in production code
        var s = temp.reduce((a, b) => a + b, 0);
        sum - s >= array[index]   && iter(index, temp.concat(array[index]));
        sum - s >= array[index+1] && iter(index + 1, temp);
        s === sum                 && result.push(temp);
        return;
    }

    var result = [];
    array.sort((x,y) => x-y); // this is a very cheap operation considering the size of the inpout array should be small for reasonable output.
    iter(0, []);
    return result;
}
var cnt = 0,
    arr = [14,6,10,7,3],
    tgt = 500,
    res;
console.time("combos");
res = getSum(arr,tgt);
console.timeEnd("combos");
console.log(`source numbers are ${arr}
found ${res.length} unique ways to sum up to ${tgt}
iter function has been called ${cnt} times`);

Часть II

Несмотря на то, что я был впечатлен производительностью, мне не понравилось вышеуказанное решение без веской причины, по которойя могу назвать.То, как это работает с побочными эффектами и очень трудно понять двойную рекурсию, и это беспокоило меня.

Итак, вот мой подход к этому вопросу.Это во много раз эффективнее по сравнению с принятым решением, хотя я работаю в JS.У нас все еще есть место, чтобы сделать это немного быстрее с безобразными императивными способами.

Разница:

Заданные числа: [14,6,10,7,3] Целевая сумма: 500

Принятый ответ:

  • Количество возможных ansers: 172686
  • Разрешается в: 26357ms
  • Количество рекурсивных вызовов: 36783575

Ответ ниже

  • Количество возможных ответов: 172686
  • Разрешение в: 1000 мс
  • Количество рекурсивных вызовов: 542657

function items2T([n,...ns],t){cnt++ //remove cnt in production code
    var c = ~~(t/n);
    return ns.length ? Array(c+1).fill()
                                 .reduce((r,_,i) => r.concat(items2T(ns, t-n*i).map(s => Array(i).fill(n).concat(s))),[])
                     : t % n ? []
                             : [Array(c).fill(n)];
};

var cnt = 0, result;
console.time("combos");
result = items2T([14, 6, 10, 7, 3], 500)
console.timeEnd("combos");
console.log(`${result.length} many unique ways to sum up to 500
and ${cnt} recursive calls are performed`);

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

function items2T([n,...ns],t){cnt++ //remove cnt in production code
    var c = ~~(t/n);
    return ns.length ? Array(c+1).fill()
                                 .reduce((r,_,i) => r.concat(items2T(ns, t-n*i).map(s => Array(i).fill(n).concat(s))),[])
                     : t % n ? []
                             : [Array(c).fill(n)];
};

var cnt = 0, result;
console.time("combos");
result = items2T([14, 10, 7, 6, 3], 500)
console.timeEnd("combos");
console.log(`${result.length} many unique ways to sum up to 500
and ${cnt} recursive calls are performed`);
0 голосов
/ 09 февраля 2019

Вы можете добавить значение фактического индекса, если сумма меньше требуемой суммы, или перейти к следующему индексу.

function getSum(array, sum) {
    function iter(index, temp) {
        var s = temp.reduce((a, b) => a + b, 0);
        if (s === sum) result.push(temp);
        if (s >= sum || index >= array.length) return;
        iter(index, temp.concat(array[index]));
        iter(index + 1, temp);
    }

    var result = [];
    iter(0, []);
    return result;
}

console.log(getSum([14, 6, 10], 40));
.as-console-wrapper { max-height: 100% !important; top: 0; }

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

function getSum(array, sum, limit) {
    function iter(index, temp) {
        var s = temp.reduce((a, b) => a + b, 0);
        if (s === sum) result.push(temp);
        if (s >= sum || index >= array.length || temp.length >= limit) return;
        iter(index, temp.concat(array[index]));
        iter(index + 1, temp);
    }

    var result = [];
    iter(0, []);
    return result;
}

console.log(getSum([14, 6, 10], 40, 5));
.as-console-wrapper { max-height: 100% !important; top: 0; }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...