Есть ли эффективный способ найти все упорядоченные расположения элементов в наборе S, которые в сумме составляют N?

#c# #.net #algorithm #linq #optimization

#c# #.net #алгоритм #linq #оптимизация

Вопрос:

Вот что я имею в виду. Предположим S = {1, 4} , и N = 5 . Упорядоченные расположения элементов в наборе S будут выглядеть так

{1}, {4}, {1,1}, {1,4}, {4,1}, {4,4}, {1,1,1}, ....

и те, которые N в сумме составляют

{1,4}, {4, 1}, {1,1,1,1,1}

Мне нужен алгоритм, который находит их эффективно.

Мой способ «грубой силы» был бы таким

 static IEnumerable<IEnumerable<int>> OrderedArrangements(IEnumerable<int> nums, int k)
{
    var singles = nums.Select(i => new int[] {i} );
    var cumulative = singles;
    for(int j = 2; j <= k;   j)
    {
        var last = cumulative.Where(list => list.Count() == (j - 1));
        var next = from x in singles
                   from y in last
                   select x.Concat(y);
        cumulative = cumulative.Concat(next);           
    }
    return cumulative;
}
  

и затем

 int sumToN = OrderedArrangements(new int[] {1, 4}, N)
                .Where(x => x.Sum() == N);
  

но мне интересно, есть ли очевидный и более эффективный способ сделать это.

Комментарии:

1. Обратите внимание, что в общем случае, если вы разрешаете использовать элементы более одного раза, ваш результат может быть бесконечно большим, если ваш ввод содержит ноль или отрицательные числа.

2. @hvd Нет нулевых или отрицательных чисел, я должен добавить

3. Было бы более эффективно начинать с N и последовательно вычитать каждый элемент из S и записывать все пути, которые заканчиваются 0? Кроме того, вы рассматриваете {1,4} и {4,1} как отдельные случаи, когда для целей вычисления они одинаковы. После этого вы можете расширить одно на другое.

4. Вы запрашиваете все «упорядоченные расположения», а затем говорите, что { 4, 1 } это есть в наборе решений, но это не в порядке. Что вы подразумеваете под «упорядоченным»?

5. @Enigmativity похоже, что здесь запрашивается набор всех кортежей, содержащих только элементы из S, которые удовлетворяют указанному условию. «Упорядоченное расположение», вероятно, следует более четко называть кортежем. Это, по-видимому, подчеркивает, что наборы (которые не имеют порядка), которые удовлетворяют указанному условию, не являются желаемыми. Терминология определенно может быть изменена для ясности.

Ответ №1:

На всякий случай, если приведенный выше ответ недостаточно ясен, вы можете попробовать прямую рекурсию, например

      ...
    /       
   (1)     (4)          
  /       /  
 (1)(4)   (1)(4)
  
 static void f(int sum, int n, String str, int[] arr){
    if (n == sum){
        Console.WriteLine(str);
        return;
    }
    if (n > sum) return;
    for (int i = 0; i < arr.Length; i  ){
        f(sum, n   arr[i], str   arr[i].ToString(), arr);
    }
}

static void Main(string[] args){
    int[] arr =  { 1, 4 };
    f(5, 0, "", arr);
}
  

Где sum находится N в вашем вопросе, n инициализируется 0 , str инициализируется "" и arr находится S в вашем вопросе.

вывод:

 11111
14
41
  

Ответ №2:

Это работает для меня:

 static IEnumerable<IEnumerable<int>> OrderedArrangements(IEnumerable<int> nums, int k)
{
    return
        k <= 0
            ? new [] { Enumerable.Empty<int>() }
            : nums
                .SelectMany(
                    n => OrderedArrangements(nums, k - n),
                    (n, ns) => new [] { n }.Concat(ns))
                .Where(ns => ns.Sum() == k);
}
  

Результатом OrderedArrangements(new [] { 1, 4 }, 5) является:

Результат


Я запустил этот код тестирования производительности:

 Func<Func<IEnumerable<IEnumerable<int>>>, double> measure = f =>
{
    var sw = Stopwatch.StartNew();
    var result = f();
    sw.Stop();
    return sw.Elapsed.TotalMilliseconds;
};

var n = 200;
var a = 0.0;
var b = 0.0;
for (var i = 0; i < n; i  )
{
    a  = measure(() => OrderedArrangements_A(new [] { 1, 4, 9, 13 }, 50000));
    b  = measure(() => OrderedArrangements_B(new [] { 1, 4, 9, 13 }, 50000));
}
  

OrderedArrangements_A это код OP и OrderedArrangements_B мой.

Я получил в среднем 15,6 мс для «A» и 0,004 мс для «B». Мой код для этого теста выполняется примерно в 3895 раз быстрее.

Комментарии:

1. Можете ли вы объяснить, почему ваше решение более эффективно, чем решение методом перебора, предложенное OP? Похоже, у OP уже есть рабочее решение. Этот, безусловно, более элегантный. но это быстрее? (Я не говорю, что это не так; просто я думаю, что хороший ответ будет отвечать на вопрос.)

2. Сказав это, вполне вероятно, что этот q amp; a лучше подходит для codereview.stackexchange.com

3. @rici — я добавил к ответу.

4. @rici — Не похоже, что решение OP работает, поэтому я не думаю, что стоит сравнивать, почему есть разница.