Подтвердить что ты не робот

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

Учитывая набор чисел: {1, 3, 2, 5, 4, 9}, найдите число подмножеств, которые суммируются с определенным значением (например, 9 для этого примера).

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

4b9b3361

Ответ 1

def total_subsets_matching_sum(numbers, sum):
    array = [1] + [0] * (sum)
    for current_number in numbers:
        for num in xrange(sum - current_number, -1, -1):
            if array[num]:
                array[num + current_number] += array[num]
    return array[sum]

assert(total_subsets_matching_sum(range(1, 10), 9)       == 8)
assert(total_subsets_matching_sum({1, 3, 2, 5, 4, 9}, 9) == 4)

Объяснение

Это одна из классических проблем. Идея состоит в том, чтобы найти количество возможных сумм с текущим числом. И это правда, что есть только один способ принести сумму в 0. В начале у нас есть только одно число. Мы начинаем с нашей цели (переменная Maximum в решении) и вычитаем это число. Если можно получить сумму этого числа (элемент массива, соответствующий этому номеру, не равен нулю), добавьте его в элемент массива, соответствующий текущему числу. Программе было бы легче понять этот путь.

for current_number in numbers:
    for num in xrange(sum, current_number - 1, -1):
        if array[num - current_number]:
            array[num] += array[num - current_number]

Когда число равно 1, есть только один способ, с помощью которого вы можете получить сумму 1 (1-1 становится 0, а элемент, соответствующий 0, равен 1). Таким образом, массив будет таким (помните, что элемент zero будет иметь 1)

[1, 1, 0, 0, 0, 0, 0, 0, 0, 0]

Теперь второе число равно 2. Мы начинаем вычитать 2 из 9 и его недействительным (поскольку элемент массива из 7 равен нулю, мы пропустим это), мы продолжаем делать это до 3. Когда его 3, 3 - 2 равно 1 и элемент массива, соответствующий 1, равен 1, и мы добавляем его к элементу массива из 3. и когда его 2, 2 - 2 становится 0, а значение соответствует 0 элементу массива из 2. После этой итерации массив выглядит следующим образом:

[1, 1, 1, 1, 0, 0, 0, 0, 0, 0]

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

[1, 1, 0, 0, 0, 0, 0, 0, 0, 0]
[1, 1, 1, 1, 0, 0, 0, 0, 0, 0]
[1, 1, 1, 2, 1, 1, 1, 0, 0, 0]
[1, 1, 1, 2, 2, 2, 2, 2, 1, 1]
[1, 1, 1, 2, 2, 3, 3, 3, 3, 3]
[1, 1, 1, 2, 2, 3, 4, 4, 4, 5]
[1, 1, 1, 2, 2, 3, 4, 5, 5, 6]
[1, 1, 1, 2, 2, 3, 4, 5, 6, 7]
[1, 1, 1, 2, 2, 3, 4, 5, 6, 8]

После последней итерации мы рассмотрели бы все числа, и количество способов получить цель было бы элементом массива, соответствующим целевому значению. В нашем случае Array [9] после последней итерации 8.

Ответ 2

Вы можете использовать динамическое программирование. Также сложность составляет O (Sum * N) и использует O (Sum) памяти.

Вот моя реализация в С#:

private static int GetmNumberOfSubsets(int[] numbers, int sum)
{
    int[] dp = new int[sum + 1];
    dp[0] = 1;
    int currentSum =0;
    for (int i = 0; i < numbers.Length; i++)
    {
        currentSum += numbers[i];
        for (int j = Math.Min(sum, currentSum); j >= numbers[i]; j--)
            dp[j] += dp[j - numbers[i]];
    }

    return dp[sum];
}

Примечания: поскольку число подмножеств может иметь значение 2 ^ N, это может легко переполнить тип int.

Альго работает только для положительных чисел.

Ответ 3

Вот Java Solution:

Это классическая проблема отслеживания Back для нахождения всех возможных подмножеств целочисленного массива или набора, который является входом, и затем filtering тех, которые суммируют с заданной target

import java.util.HashSet;
import java.util.StringTokenizer;

/**
 * Created by anirudh on 12/5/15.
 */
public class findSubsetsThatSumToATarget {

    /**
     * The collection for storing the unique sets that sum to a target.
     */
    private static HashSet<String> allSubsets = new HashSet<>();

    /**
     * The String token
     */
    private static final String token = " ";

    /**
     * The method for finding the subsets that sum to a target.
     *
     * @param input  The input array to be processed for subset with particular sum
     * @param target The target sum we are looking for
     * @param ramp   The Temporary String to be beefed up during recursive iterations(By default value an empty String)
     * @param index  The index used to traverse the array during recursive calls
     */
    public static void findTargetSumSubsets(int[] input, int target, String ramp, int index) {

        if(index > (input.length - 1)) {
            if(getSum(ramp) == target) {
                allSubsets.add(ramp);
            }
            return;
        }

        //First recursive call going ahead selecting the int at the currenct index value
        findTargetSumSubsets(input, target, ramp + input[index] + token, index + 1);
        //Second recursive call going ahead WITHOUT selecting the int at the currenct index value
        findTargetSumSubsets(input, target, ramp, index + 1);
    }

    /**
     * A helper Method for calculating the sum from a string of integers
     *
     * @param intString the string subset
     * @return the sum of the string subset
     */
    private static int getSum(String intString) {
        int sum = 0;
        StringTokenizer sTokens = new StringTokenizer(intString, token);
        while (sTokens.hasMoreElements()) {
            sum += Integer.parseInt((String) sTokens.nextElement());
        }
        return sum;
    }

    /**
     * Cracking it down here : )
     *
     * @param args command line arguments.
     */
    public static void main(String[] args) {
        int [] n =  {24, 1, 15, 3, 4, 15, 3};
        int counter = 1;
        FindSubsetsThatSumToATarget.findTargetSumSubsets(n, 25, "", 0);
        for (String str: allSubsets) {
            System.out.println(counter + ") " + str);
            counter++;
        }
    }
}

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

Распечатал бы разделенные запятыми значения для подмножеств с суммой 25 в {24, 1, 15, 3, 4, 15, 3}

1) 24 1

2) 3 4 15 3

3) 15 3 4 3

Ответ 4

Тот же сайт geeksforgeeks также обсуждает решение для вывода всех подмножеств, которые суммируются с определенным значением: http://www.geeksforgeeks.org/backttracking-set-4-subset-sum/

В вашем случае вместо наборов вывода вам просто нужно их подсчитать. Обязательно проверьте оптимизированную версию на той же странице, что и проблема NP-complete.

Этот вопрос также задавался и отвечал ранее в stackoverflow без упоминания о том, что это проблема с подмножеством: Поиск всех возможных комбинаций чисел для достижения заданной суммы

Ответ 5

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

array = [1, 3, 4, 2, 7, 8, 9]

0..array.size.times.each do |i| 
  @ary.combination(i).to_a.each { |a| print a if a.inject(:+) == 9} 
end

Ответ 6

Я решил это java. Это решение довольно просто.

import java.util.*;

public class Recursion {

static void sum(int[] arr, int i, int sum, int target, String s)
{   
    for(int j = i+1; j<arr.length; j++){
        if(sum+arr[j] == target){
            System.out.println(s+" "+String.valueOf(arr[j]));
        }else{
            sum(arr, j, sum+arr[j], target, s+" "+String.valueOf(arr[j]));
        }
    }
}

public static void main(String[] args)
{   
    int[] numbers = {6,3,8,10,1};
    for(int i =0; i<numbers.length; i++){
        sum(numbers, i, numbers[i], 18, String.valueOf(numbers[i])); 
    }

}
}

Ответ 7

Обычное решение DP верно для проблемы.

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

Ответ 8

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

function getSummingItems(a,t){
  return a.reduce((h,n) => Object.keys(h)
                                 .reduceRight((m,k) => +k+n <= t ? (m[+k+n] = m[+k+n] ? m[+k+n].concat(m[k].map(sa => sa.concat(n)))
                                                                                      : m[k].map(sa => sa.concat(n)),m)
                                                                 :  m, h), {0:[[]]})[t];
}
var arr = Array(20).fill().map((_,i) => i+1), // [1,2,..,20]
    tgt = 42,
    res = [];

console.time("test");
res = getSummingItems(arr,tgt);
console.timeEnd("test");
console.log("found",res.length,"subsequences summing to",tgt);
console.log(JSON.stringify(res));

Ответ 9

public class SumOfSubSet {

    public static void main(String[] args) {
        // TODO Auto-generated method stub

        int a[] = {1,2};
        int sum=0;
        if(a.length<=0) {
            System.out.println(sum);
        }else {
        for(int i=0;i<a.length;i++) {
            sum=sum+a[i];
            for(int j=i+1;j<a.length;j++) {
                sum=sum+a[i]+a[j];
            }
        }
        System.out.println(sum);

        }


    }

}

Ответ 10

РУБИН

Этот код отклоняет пустые массивы и возвращает правильный массив со значениями.

def find_sequence(val, num)
  b = val.length
  (0..b - 1).map {|n| val.uniq.combination(n).each.find_all {|value| value.reduce(:+) == num}}.reject(&:empty?)
end

val = [-10, 1, -1, 2, 0]
num = 2

Вывод будет [[2], [2,0], [-1, 1,2], [-1, 1,2,0]]

Ответ 11

Мое решение по возврату: - Сортируйте массив, затем примените возврат.

void _find(int arr[],int end,vector<int> &v,int start,int target){
        if(target==0){
        for(int i = 0;i<v.size();i++){
            cout<<v[i]<<" ";
        }
        cout<<endl;
    }

    else{
        for(int i =  start;i<=end && target >= arr[i];i++){
            v.push_back(arr[i]);
            _find(arr,end,v,i+1,target-arr[i]);
            v.pop_back();
        }
    }
}

Ответ 12

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

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

Решение состоит в том, чтобы использовать строку для отслеживания последовательности. Присоединение к строке можно сделать встроенным в вызов функции; тем самым поддерживая тот же кадр стека, и ваш ответ будет прекрасно соответствовать исходной рекурсивной структуре hasSubSetSum.

import java.util.ArrayList;

Public Class Solution {

public static boolean hasSubSet(int [] A, int target) {
    ArrayList<String> subsets = new ArrayList<>();
    helper(A, target, 0, 0, subsets, "");
    // Printing the contents of subsets is straightforward
    return !subsets.isEmpty();
}

private static void helper(int[] A, int target, int sumSoFar, int i, ArrayList<String> subsets, String curr) {
    if(i == A.length) {
        if(sumSoFar == target) {
            subsets.add(curr);
        }
        return;
    }
    helper(A, target, sumSoFar, i+1, subsets, curr);
    helper(A, target, sumSoFar + A[i], i+1, subsets, curr + A[i]);
}

public static void main(String [] args) {
    System.out.println(hasSubSet(new int[] {1,2,4,5,6}, 8));
}

}

Ответ 13

Задача суммы подмножеств может быть решена в O (сумма * n) с помощью динамического программирования. Оптимальная подструктура для подмножества суммы выглядит следующим образом:

SubsetSum (A, n, sum) = SubsetSum (A, n-1, sum) || SubsetSum (A, n-1, sum-set [n-1])

SubsetSum (A, n, sum) = 0, если sum> 0 и n == 0 SubsetSum (A, n, sum) = 1, если sum == 0

Здесь A - массив элементов, n - количество элементов массива A, а sum - сумма элементов в подмножестве.

Используя этот дп, вы можете определить количество подмножеств для суммы.

Для получения элементов подмножества мы можем использовать следующий алгоритм:

После заполнения dp [n] [sum] путем вызова SubsetSum (A, n, sum) мы рекурсивно обходим его из dp [n] [sum]. Для прохождения ячейки мы сохраняем путь до его достижения и рассматриваем две возможности для элемента.

1) Элемент включен в текущий путь.

2) Элемент не включен в текущий путь.

Всякий раз, когда сумма становится 0, мы прекращаем рекурсивные вызовы и печатаем текущий путь.

void findAllSubsets(int dp[], int A[], int i, int sum, vector<int>& p) { 

   if (sum == 0) { 
        print(p); 
        return; 
   } 

   // If sum can be formed without including current element
   if (dp[i-1][sum]) 
   { 
        // Create a new vector to store new subset 
        vector<int> b = p; 
        findAllSubsets(dp, A, i-1, sum, b); 
   } 

   // If given sum can be formed after including 
   // current element. 
   if (sum >= A[i] && dp[i-1][sum-A[i]]) 
   { 
        p.push_back(A[i]); 
        findAllSubsets(dp, A, i-1, sum-A[i], p); 
   } 

} 

Ответ 14

Следующее решение также предоставляет массив подмножеств, которые обеспечивают конкретную сумму (здесь сумма = 9)

array = [1, 3, 4, 2, 7, 8, 9]

(0..array.size).map { |i| array.combination(i).to_a.select { |a| a.sum == 9 } }.flatten(1)

вернуть массив подмножеств, которые возвращают сумму 9

 => [[9], [1, 8], [2, 7], [3, 4, 2]] 

Ответ 15

Вот эффективное решение, когда есть большой набор входов (то есть от 25 до 30)

Я добился повышения эффективности двумя способами:

  • Использовал простую концепцию вращающегося колеса для двоичного счета во всех возможных итерациях, вместо использования математически дорогих возможностей базового языка преобразования. Это "колесо" сродни старому механическому счетчику или одометру. Он рекурсивно перемещает столько позиций, сколько необходимо, пока мы не превысим количество двоичных цифр, которые у нас есть (например, количество чисел в нашем наборе).
  • Основное преимущество заключается в том, что каждый раз не суммируется весь набор кандидатов. Вместо этого он поддерживает сумму бега и каждый раз, когда он "катит колесо", он корректирует сумму бега только для фигур, которые изменились из последнего набора кандидатов, который он тестировал. Это экономит много вычислений, так как большинство "барабанов колес" меняют только число или два.

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

На моем старом настольном компьютере 2012-го года данный код обрабатывает 25 входных значений примерно за 0,8 секунды в javascript/node.js и 3,4 секунды в С#.

Javascript

let numbers = [-0.47, -0.35, -0.19, 0.23, 0.36, 0.47, 0.51, 0.59, 0.63, 0.79, 0.85, 
0.91, 0.99, 1.02, 1.17, 1.25, 1.39, 1.44, 1.59, 1.60, 1.79, 1.88, 1.99, 2.14, 2.31];

let target = 24.16;

displaySubsetsThatSumTo(target, numbers);

function displaySubsetsThatSumTo(target, numbers)
{
    let wheel = [0];
    let resultsCount = 0;
    let sum = 0;

    const start = new Date();
    do {
        sum = incrementWheel(0, sum, numbers, wheel);
        //Use subtraction comparison due to javascript float imprecision
        if (sum != null && Math.abs(target - sum) < 0.000001) {
            //Found a subset. Display the result.
            console.log(numbers.filter(function(num, index) {
                return wheel[index] === 1;
            }).join(' + ') + ' = ' + target);
            resultsCount++;
        }
    } while (sum != null);
    const end = new Date();

    console.log('--------------------------');
    console.log('Processed ${numbers.length} numbers in ${(end - start) / 1000} seconds (${resultsCount} results)');
}

function incrementWheel(position, sum, numbers, wheel) {
    if (position === numbers.length || sum === null) {
        return null;
    }
    wheel[position]++;
    if (wheel[position] === 2) {
        wheel[position] = 0;
        sum -= numbers[position];
        if (wheel.length < position + 2) {
            wheel.push(0);
        }
        sum = incrementWheel(position + 1, sum, numbers, wheel);
    }
    else {
        sum += numbers[position];
    }
    return sum;
}

С#

    public class Program
    {
        static void Main(string[] args)
        {
            double[] numbers = { -0.47, -0.35, -0.19, 0.23, 0.36, 0.47, 0.51, 0.59, 0.63, 0.79, 0.85,
                0.91, 0.99, 1.02, 1.17, 1.25, 1.39, 1.44, 1.59, 1.60, 1.79, 1.88, 1.99, 2.14, 2.31 };

            double target = 24.16;

            DisplaySubsetsThatSumTo(target, numbers);
        }

        private static void DisplaySubsetsThatSumTo(double Target, double[] numbers)
        {
            var stopwatch = new System.Diagnostics.Stopwatch();

            bool[] wheel = new bool[numbers.Length];
            int resultsCount = 0;
            double? sum = 0;

            stopwatch.Start();

            do
            {
                sum = IncrementWheel(0, sum, numbers, wheel);
                //Use subtraction comparison due to double type imprecision
                if (sum.HasValue && Math.Abs(sum.Value - Target) < 0.000001F)
                {
                    //Found a subset. Display the result.
                    Console.WriteLine(string.Join(" + ", numbers.Where((n, idx) => wheel[idx])) + " = " + Target);
                    resultsCount++;
                }
            } while (sum != null);

            stopwatch.Stop();

            Console.WriteLine("--------------------------");
            Console.WriteLine($"Processed {numbers.Length} numbers in {stopwatch.ElapsedMilliseconds / 1000.0} seconds ({resultsCount} results). Press any key to exit.");
            Console.ReadKey();
        }

        private static double? IncrementWheel(int Position, double? Sum, double[] numbers, bool[] wheel)
        {
            if (Position == numbers.Length || !Sum.HasValue)
            {
                return null;
            }
            wheel[Position] = !wheel[Position];
            if (!wheel[Position])
            {
                Sum -= numbers[Position];
                Sum = IncrementWheel(Position + 1, Sum, numbers, wheel);
            }
            else
            {
                Sum += numbers[Position];
            }
            return Sum;
        }
    }

Выход

-0.35 + 0.23 + 0.36 + 0.47 + 0.51 + 0.59 + 0.63 + 0.79 + 0.85 + 0.91 + 0.99 + 1.02 + 1.17 + 1.25 + 1.44 + 1.59 + 1.6 + 1.79 + 1.88 + 1.99 + 2.14 + 2.31 = 24.16
0.23 + 0.51 + 0.59 + 0.63 + 0.79 + 0.85 + 0.99 + 1.02 + 1.17 + 1.25 + 1.39 + 1.44 + 1.59 + 1.6 + 1.79 + 1.88 + 1.99 + 2.14 + 2.31 = 24.16
-0.47 + 0.23 + 0.47 + 0.51 + 0.59 + 0.63 + 0.79 + 0.85 + 0.99 + 1.02 + 1.17 + 1.25 + 1.39 + 1.44 + 1.59 + 1.6 + 1.79 + 1.88 + 1.99 + 2.14 + 2.31 = 24.16
-0.19 + 0.36 + 0.51 + 0.59 + 0.63 + 0.79 + 0.91 + 0.99 + 1.02 + 1.17 + 1.25 + 1.39 + 1.44 + 1.59 + 1.6 + 1.79 + 1.88 + 1.99 + 2.14 + 2.31 = 24.16
-0.47 + -0.19 + 0.36 + 0.47 + 0.51 + 0.59 + 0.63 + 0.79 + 0.91 + 0.99 + 1.02 + 1.17 + 1.25 + 1.39 + 1.44 + 1.59 + 1.6 + 1.79 + 1.88 + 1.99 + 2.14 + 2.31 = 24.16
0.23 + 0.47 + 0.51 + 0.63 + 0.85 + 0.91 + 0.99 + 1.02 + 1.17 + 1.25 + 1.39 + 1.44 + 1.59 + 1.6 + 1.79 + 1.88 + 1.99 + 2.14 + 2.31 = 24.16
--------------------------
Processed 25 numbers in 0.823 seconds (6 results)