问题
I have an array of random values, and a target value.
#!/bin/bash
objective='50'
declare -a values=(1 2 2 6 8 14.5 15 28.7 .. 42)
I need to find a way to extract any combination of numbers in the array 'values' that add up to 50
The array has duplicates, and floating point integers.
A solution set might look like:
50 = 42 + 8
50 = 42 + 6 + 2
Initially I started in bash with some nested for loops, however I'm quickly realizing that this will grow exponentially with my array length.
I took a couple of java classes in college, but I'm still inexperienced in programming. I'm starting to think this may require recursion.
Can anyone with more programming experience point me in the right direction?
Besides nested for loops, how else could you approach this problem?
回答1:
Here is an algorithm that has time complexity O(M*N) whereas M is Target and N is total size of set. Use analogy with knapsack problem as follows :-
- Knapsack capacity = Target
- Items are elements in the set with weight & value same as itself
- Calculate maximum profit using dynamic programming
- maxprofit = Target then there is/are subset which sum up to target.
- Retrace the solution.
Java Solution for the same :-
public class SubSetSum {
static int[][] costs;
public static void calSets(int target,int[] arr) {
costs = new int[arr.length][target+1];
for(int j=0;j<=target;j++) {
if(arr[0]<=j) {
costs[0][j] = arr[0];
}
}
for(int i=1;i<arr.length;i++) {
for(int j=0;j<=target;j++) {
costs[i][j] = costs[i-1][j];
if(arr[i]<=j) {
costs[i][j] = Math.max(costs[i][j],costs[i-1][j-arr[i]]+arr[i]);
}
}
}
System.out.println(costs[arr.length-1][target]);
if(costs[arr.length-1][target]==target) {
System.out.println("Sets :");
printSets(arr,arr.length-1,target,"");
}
else System.out.println("No such Set found");
}
public static void printSets(int[] arr,int n,int w,String result) {
if(w==0) {
System.out.println(result);
return;
}
if(n==0) {
System.out.println(result+","+arr[0]);
return;
}
if(costs[n-1][w]==costs[n][w]) {
printSets(arr,n-1,w,new String(result));
}
if(arr[n]<=w&&(costs[n-1][w-arr[n]]+arr[n])==costs[n][w]) {
printSets(arr,n-1,w-arr[n],result+","+arr[n]);
}
}
public static void main(String[] args) {
int[] arr = {1,2,3,8,9,7};
calSets(10,arr);
}
}
回答2:
I would do it like this:
1.some init
const int N=array size;
int val[N]; // input data
bool flag[N]; // is val used ?
for (int i=0;i<N;i++) flag[i]=false;
sort val[] descending
2.create function bool find_sum(int s);
- if it found solution returns true else false
set flag to true for all used values
{ for (int i=0;i<N;i++) // find first usable big number (could be faster with binary search or with tailing last used i index into recursion) if ((val[i]<=s)&&(!flag[i])) { flag[i]=true; // flag it as used if (val[i]==s) return true; // try to find reminder if (find_sum(s-val[i])) return true; // if found return true flag[i]=false; // else unflag val[i] and continue winth next number } return false; // if sum not found then return false }
3.after find_sum(s) your sum consists of all val[i] where flag[i]!=false
[edit1] functional source tested even for your case [6,5,5] and sum=10 it is OK
//---------------------------------------------------------------------------
int find_sum(int *val,int *use,int N,int sum,bool init=true)
{
int i;
if (init)
{
for (i=0;i<N;i++) use[i]=0; // nothibg used yet
for (int e=1;e;) // bubble sort
for (e=0,i=1;i<N;i++)
if (val[i-1]<val[i])
{ e=val[i-1]; val[i-1]=val[i]; val[i]=e; e=1; }
}
for (i=0;i<N;i++) // find first usable big number (could be faster with binary search or with tailing last used i index into recursion)
if ((val[i]<=sum)&&(!use[i]))
{
use[i]=1; // val[i] is used
if (val[i]==sum) return 1; // try to find reminder
if (find_sum(val,use,N,sum-val[i],false)) return 1; // if found return true
use[i]=0; // else val[i] is unused and continue winth next number
}
return 0; // if sum not found then return false
}
//---------------------------------------------------------------------------
void main()
{
int in[]={6,5,5}; // input data
const int N=sizeof(in)/sizeof(int);
int ret,out[N];
if (find_sum(in,out,N,10))
for (int i=0;i<N;i++)
if (out[i])
{
cout << in[i] << " ";
}
}
//---------------------------------------------------------------------------
PS. in your question in the input array are also floating point values
- so you have to change int val[],sum to float/double
and add some accuracy for sum comparison to work with floats
if (val[i]==sum) return 1;change to
if (fabs(val[i]-sum)<1e-10) return 1;or use any other accuracy instead of 1e-10
回答3:
You can use recursion yes, you can break the array into sub-parts (I use List).
- Start from 0th index of the Parent list and a blank list
- Iterate over to subList your Parent from
i+1to the end and thereby increasing your working list from0 to i - Check for the
sum(calculated) equals your objective
Code:
static Integer[] array = { 1, 2, 2, 6, 8, 14, 15, 28, 30, 32, 12, 48, 6, 42 };
static int objective = 50;
public static void main(String args[]) {
add(new ArrayList<Integer>(Arrays.asList(array)),
new ArrayList<Integer>());
}
public static void add(List<Integer> digits, List<Integer> workingList) {
for (int i = 0; i < digits.size(); i++) {
// New sublist to store values from 0 to i
List<Integer> list = new ArrayList<Integer>(workingList);
list.add(digits.get(i));
// Here you call this recursively with your Parent list from i+1
// index and working list from 0 to i
add(digits.subList(i + 1, digits.size()), list);
}
int sum = 0;
for (int element : workingList) {
sum += element;
}
if (sum == objective) {
System.out.println(objective + " = "
+ Arrays.toString(workingList.toArray()));
}
}
Output:
50 = [1, 2, 2, 15, 30]
50 = [1, 2, 6, 8, 15, 12, 6]
50 = [1, 2, 6, 14, 15, 12]
50 = [1, 2, 14, 15, 12, 6]
50 = [1, 2, 15, 32]
50 = [1, 2, 6, 8, 15, 12, 6]
...
来源:https://stackoverflow.com/questions/21670732/how-to-find-if-any-values-in-array-add-up-to-n