使用给定集合中的值计算获得N的所有可能性

时间:2013-08-20 18:19:53

标签: arrays algorithm

所以这就是问题所在:

鉴于input = [100 80 66 25 4 2 1],我需要找到最佳组合给我50。

看看这个,最好的是25 + 25 = 50,所以我需要数组中的2个元素。

其他组合包括25+4+4+4+4+4+4+125+4+4+4+4+4+2+2+1等等

我需要找到所有可能性,这些可能性给出了我想要的值的总和。

编辑:以及最好的可能性(一个条款数量最少的一个)

这是我到目前为止所做的事情: 首先构建一个新的数组(循环的简单循环遍历所有元素并存储在新的临时数组中),检查高于我的数组的所有元素(因此对于输入50,元素100,80,66更高,所以丢弃它们然后我的新阵列是[25 4 2 1])。然后,由此,我需要检查组合。

我做的第一件事是一个简单的if语句,检查是否有任何数组元素与我想要的数字完全匹配。所以,如果我想要50,我会检查阵列中是否有50,如果没有,我需要找到组合。

我的问题是,我不完全确定如何找到每一个组合。我一直在努力尝试提出一段时间的算法,但我总是最终陷入困境。

非常感谢任何帮助/提示。

PS - 我们可以假设数组总是按照从LARGEST到SMALLEST值的顺序排序。

10 个答案:

答案 0 :(得分:5)

这是动态编程要解决的问题。

创建一个索引为1到50的数组。将每个条目设置为-1。对于输入数组中的每个元素,将数组中的元素设置为0.然后,对于n = 2到50的每个整数,找到所有可能的求和方式。所需的总和数是两个加数中的最小值加1.最后,将元素放在索引50处。

答案 1 :(得分:3)

编辑:由于对问题的误解,我首先回答了一种有效的方法来计算可能性的数量(而不是自己的可能性)来获得N使用给定集合中的值。该解决方案可以在本文的底部找到,作为其他人的参考,但首先我会对你的问题给出正确的答案。


生成所有可能性,计算它们并给出最短的

在生成解决方案时,您会考虑输入数组中的每个元素并问自己“我应该在我的解决方案中使用它吗?”。由于我们在计算之后才知道答案,因此我们只需要尝试使用它而不使用它,如下面代码中的递归步骤所示。

现在,为了避免重复和遗漏,我们需要对递归调用的参数稍微小心。如果我们使用当前元素,我们还应该允许它在下一步中使用,因为元素可能会被使用多次。因此,此递归调用中的第一个参数是i。但是,如果我们决定不使用元素,我们应该允许它在下一步中使用,因为这将是当前步骤的重复。因此,此递归调用中的第一个参数是i+1

我在算法中添加了一个可选的绑定(从“branch and bound”),如果已知此解决方案永远不会短于目前为止找到的最短解决方案,则会停止扩展当前的部分解决方案。 / p>

package otherproblems;

import java.util.Deque;
import java.util.LinkedList;

public class GeneratePossibilities
{
    // Input
    private static int n = 50;
    // If the input array is sorted ascending, the shortest solution is
    // likely to be found somewhere at the end.
    // If the input array is sorted descending, the shortest solution is
    // likely to be found somewhere in the beginning.
    private static int[] input = {100, 80, 66, 25, 4, 2, 1};

    // Shortest possibility
    private static Deque<Integer> shortest;
    // Number of possibilities
    private static int numberOfPossibilities;

    public static void main(String[] args)
    {
        calculate(0, n, new LinkedList<Integer>());
        System.out.println("\nAbove you can see all " + numberOfPossibilities +
            " possible solutions,\nbut this one's the shortest: " + shortest);
    }

    public static void calculate(int i, int left, Deque<Integer> partialSolution)
    {
        // If there's nothing left, we reached our target
        if (left == 0)
        {
            System.out.println(partialSolution);
            if (shortest == null || partialSolution.size() < shortest.size())
                shortest = new LinkedList<Integer>(partialSolution);
            numberOfPossibilities++;
            return;
        }
        // If we overshot our target, by definition we didn't reach it
        // Note that this could also be checked before making the
        // recursive call, but IMHO this gives a cleaner recursion step.
        if (left < 0)
            return;
        // If there are no values remaining, we didn't reach our target
        if (i == input.length)
            return;

        // Uncomment the next two lines if you don't want to keep generating
        // possibilities when you know it can never be a better solution then
        // the one you have now.
//      if (shortest != null && partialSolution.size() >= shortest.size())
//          return;

        // Pick value i. Note that we are allowed to pick it again,
        // so the argument to calculate(...) is i, not i+1.
        partialSolution.addLast(input[i]);
        calculate(i, left-input[i], partialSolution);
        // Don't pick value i. Note that we are not allowed to pick it after
        // all, so the argument to calculate(...) is i+1, not i.
        partialSolution.removeLast();
        calculate(i+1, left, partialSolution);
    }

}

有效计算可能性的数量

这是动态编程的一个很好的例子。您需要做的是确定形成数字x的可能性有多少,使用值y作为最后一个加法并且仅使用小于或等于y的值。这为您提供了一个递归公式,您可以使用动态编程轻松转换为解决方案。我不太清楚如何在这里写下数学,但是既然你对它们不感兴趣,这里是解决你问题的代码:)

import java.util.Arrays;

public class Possibilities
{
    public static void main(String[] args)
    {
        // Input
        int[] input = {100, 80, 66, 25, 4, 2, 1};
        int n = 50;

        // Prepare input
        Arrays.sort(input);

        // Allocate storage space
        long[][] m = new long[n+1][input.length];

        for (int i = 1; i <= n; i++)
            for (int j = 0; j < input.length; j++)
            {
                // input[j] cannot be the last value used to compose i
                if (i < input[j])
                    m[i][j] = 0;
                // If input[j] is the last value used to compose i,
                // it must be the only value used in the composition.
                else if (i == input[j])
                    m[i][j] = 1;
                // If input[j] is the last value used to compose i,
                // we need to know the number of possibilities in which
                // i - input[j] can be composed, which is the sum of all
                // entries in column m[i-input[j]].
                // However, to avoid counting duplicates, we only take
                // combinations that are composed of values equal or smaller
                // to input[j].
                else
                    for (int k = 0; k <= j; k++)
                        m[i][j] += m[i-input[j]][k];
            }

        // Nice output of intermediate values:
        int digits = 3;
        System.out.printf(" %"+digits+"s", "");
        for (int i = 1; i <= n; i++)
            System.out.printf(" %"+digits+"d", i);
        System.out.println();
        for (int j = 0; j < input.length; j++)
        {
            System.out.printf(" %"+digits+"d", input[j]);
            for (int i = 1; i <= n; i++)
                System.out.printf(" %"+digits+"d", m[i][j]);
            System.out.println();
        }

        // Answer:
        long answer = 0;
        for (int i = 0; i < input.length; i++)
            answer += m[n][i];
        System.out.println("\nThe number of possibilities to form "+n+
            " using the numbers "+Arrays.toString(input)+" is "+answer);
    }
}

答案 2 :(得分:1)

这是integer knapsack problem,这是你最常见的NP完全问题之一;如果你进入算法设计/研究检查那些。为了找到最好的我认为你别无选择,只能计算它们并保持最小的一个。

对于正确的解决方案,有一个非常简单的递归算法。

import org.apache.commons.lang.ArrayUtils;
import java.util.*;

public class Stuff {

    private final int target;
    private final int[] steps;


    public Stuff(int N, int[] steps) {
        this.target = N;
        this.steps = Arrays.copyOf(steps, steps.length);
        Arrays.sort(this.steps);
        ArrayUtils.reverse(this.steps);
        this.memoize = new HashMap<Integer, List<Integer>>(N);
    }

    public List<Integer> solve() {
        return solveForN(target);
    }

    private List<Integer> solveForN(int N) {
        if (N == 0) {
            return new ArrayList<Integer>();
        } else if (N > 0) {
            List<Integer> temp, min = null;
            for (int i = 0; i < steps.length; i++) {
                temp = solveForN(N - steps[i]);
                if (temp != null) {
                    temp.add(steps[i]);
                    if (min == null || min.size() > temp.size()) {
                        min = temp;
                    }
                }
            }
            return min;
        } else {
            return null;
        }
    }
}

这是基于这样的事实:“到达N”你来自N - 步[0],或N - 步1,......

因此,您从目标总N开始并减去其中一个可能的步骤,并再次执行,直到您处于0(返回List以指定这是一个有效路径)或更低(返回null,这样您就不能返回无效路径。)

这种正确解决方案的复杂性是指数级的!哪个真的很糟糕!类似O(k ^ M)的东西,其中M是steps数组的大小,k是常数。

要在更短的时间内解决此问题,您必须使用启发式(近似),并且您将始终有一定的概率得到错误​​的答案。

通过记住目前为所有目标看到的最短组合,您可以更快地实现自己的实现(因此,如果您已经这样做,则不需要重新计算recur(N,_,步))。这种方法称为动态编程。我会让你自己这样做(非常有趣的东西,真的不那么复杂)。

此解决方案的约束:如果您保证输入数组(步骤)按降序排序并按顺序执行,则只能找到解决方案。

如果您还想查看近似解决方案,请参阅一般背包问题的链接:http://en.wikipedia.org/wiki/Knapsack_problem

答案 3 :(得分:0)

您需要解决每个子问题并存储解决方案。例如:

1只能是1. 2可以是2或1 + 1。 4可以是4或2 + 2或2 + 1 + 1或1 + 1 + 1 + 1。所以你采取每个子解决方案并存储它,所以当你看到25 = 4 + 4 + 4 + 4 + 4 + 4 + 1时,你已经知道每个4也可以表示为3种组合中的一种。

然后你必须对数字进行排序并检查以避免重复的模式,例如,(2 + 2)+(2 + 2)+(2 + 2)+(1 + 1 + 1 + 1)+( 1 + 1 + 1 + 1)+(1 + 1 + 1 + 1)==(2 + 1 + 1)+(2 + 1 + 1)+(2 + 1 + 1)+(2 + 1 + 1 )+(2 + 1 + 1)+(2 + 1 + 1)。两种情况下都有6个2和12个1。

这有意义吗?

答案 4 :(得分:0)

递归应该是解决这个问题的最简单方法(假设你真的想找到问题的所有解决方案)。这种方法的好处是,如果你想找到最短的解决方案,你可以添加一个检查递归并找到它,节省时间和空间:)

假设数组的元素i是解决方案的一部分,您可以解决查找总和为n-i的元素的子问题。如果我们为我们的解决方案添加一个排序,例如总和中的数字必须从大到小,我们就有办法找到独特的解决方案。

这是C#中的递归解决方案,应该很容易在java中进行翻译。

    public static void RecursiveSum(int n, int index, List<int> lst, List<int> solution)
    {
        for (int i = index; i < lst.Count; i++)
        {
            if (n == 0)
            {
                Console.WriteLine("");
                foreach (int j in solution)
                {
                    Console.Write(j + " ");
                }
            }
            if (n - lst[i] >= 0)
            {
                List<int> tmp = new List<int>(solution);
                tmp.Add(lst[i]);
                RecursiveSum(n - lst[i], i, lst, tmp);
            }
        }
    }

你用

来调用它
RecursiveSum(N,0,list,new List<int>());

其中N是您要查找的总和,0不应该更改,list是您允许的数字列表,最后一个参数也不应该更改。

答案 5 :(得分:0)

使用贪心算法n次(n是数组中元素的数量),每次弹出列表中最大的元素。例如。 (用一些随机的伪代码语言):

array = [70 30 25 4 2 1]
value = 50

sort(array, descending)

solutions = []  // array of arrays

while length of array is non-zero:
    tmpValue = value
    thisSolution = []
    for each i in array:
        while tmpValue >= i:
            tmpValue -= i
            thisSolution.append(i)

    solutions.append(thisSolution)

    array.pop_first() // remove the largest entry from the array

如果使用设置[70 30 25 4 2 1]50运行,它应该为您提供一个solutions数组:

[[30 4 4 4 4 4]
 [30 4 4 4 4 4]
 [25 25]
 [4 4 4 4 4 4 4 4 4 4 4 4 2]
 [2 ... ]
 [1 ... ]]

然后简单地从解决方案数组中选择具有最小长度的元素。

更新:评论是正确的,在所有情况下都不会生成正确的答案。原因是贪婪并不总是正确的。以下递归算法应始终有效:

array = [70, 30, 25, 4, 3, 1]

def findSmallest(value, array):
    minSolution = []
    tmpArray = list(array)

    while len(tmpArray):
        elem = tmpArray.pop(0)
        tmpValue = value

        cnt = 0
        while tmpValue >= elem:
            cnt += 1
            tmpValue -= elem

            subSolution = findSmallest(tmpValue, tmpArray)

            if tmpValue == 0 or subSolution:
                if not minSolution or len(subSolution) + cnt < len(minSolution):
                    minSolution = subSolution + [elem] * cnt

    return minSolution

print findSmallest(10, array)
print findSmallest(50, array)
print findSmallest(49, array)
print findSmallest(55, array)

打印:

[3, 3, 4]
[25, 25]
[3, 4, 4, 4, 4, 30]
[30, 25]

不变量是函数返回传入值的最小集合,或者返回空集合。然后可以递归地使用它与列表中先前数字的所有可能值。请注意,这是O(n!)的复杂性,因此对于较大的值来说会很慢。另请注意,这里有许多优化潜力。

答案 6 :(得分:0)

这不仅仅是一个搜索问题吗?如果是这样,只需搜索广度优先。

abstract class Numbers {
  abstract int total();

  public static Numbers breadthFirst(int[] numbers, int total) {
    List<Numbers> stack = new LinkedList<Numbers>();
    if (total == 0) { return new Empty(); }
    stack.add(new Empty());
    while (!stack.isEmpty()) {
      Numbers nums = stack.remove(0);
      for (int i : numbers) {
        if (i > 0 && total - nums.total() >= i) {
          Numbers more = new SomeNumbers(i, nums);
          if (more.total() == total) { return more; }
          stack.add(more);
        }
      }
    }
    return null;  // No answer.
  }
}

class Empty extends Numbers {
  int total() { return 0; }
  public String toString() { return "empty"; }
}
class SomeNumbers extends Numbers {
  final int total;
  final Numbers prev;
  SomeNumbers(int n, Numbers prev) {
    this.total = n + prev.total();
    this.prev = prev;
  }
  int total() { return total; }
  public String toString() {
    if (prev.getClass() == Empty.class) { return "" + total; }
    return prev + "," + (total - prev.total());
  }

}

答案 7 :(得分:0)

你提出的问题很有趣但非常复杂。我会通过使用像OptaPlanner(以前的Drools Planner)这样的方法来解决这个问题。在不花费大量时间的情况下很难描述这个问题的完整解决方案,但是通过optaplanner,您还可以获得“最贴近”类型的答案,并且可以使用增量“移动”来提高解决问题的效率。祝你好运。

答案 8 :(得分:0)

这是python中的一个解决方案:Ideone link

# Start of tsum function
def tsum(currentSum,total,input,record,n):
     if total == N :
        for i in range(0,n):
            if record[i]:
                print input[i]

            i = i+1
            for i in range(i,n):
                if record[i]:
                    print input[i]
            print ""
            return
     i=currentSum
     for i in range(i,n):
         if total+input[i]>sum :
             continue
         if i>0 and input[i]==input[i-1] and not record[i-1] :
             continue
         record[i]=1
         tsum(i+1,total+input[i],input,record,l)
         record[i]=0

# end of function
# Below portion will be main() in Java
record = []
N = 5
input = [3, 2, 2, 1, 1]
temp = list(set(input))
newlist = input
for i in range(0, len(list(set(input)))):
    val = N/temp[i]
    for j in range(0, val-input.count(temp[i])):
        newlist.append(temp[i])

# above logic was to create a newlist/input i.e [3, 2, 2, 1, 1, 1, 1, 1] 
# This new list contains the maximum number of elements <= N 
# for e.g appended three 1's as sum of new three 1's + existing two 1's <= N(5) where as
# did not append another 2 as 2+2+2 > N(5) or 3 as 3+3 > N(5)

l = len(input)

for i in range(0,l):
    record.append(0)
print "all possibilities to get N using values from a given set:"

tsum(0,0,input,record,l)

OUTPUT:用于设置[3,2,2,1,1]取小集和小N用于演示目的。但也适用于更高的N值。

对于N = 5

使用给定集合中的值获取N的所有可能性: 3 2

3 1 1

2 2 1

2 1 1 1

1 1 1 1 1

对于N = 3

使用给定集合中的值获取N的所有可能性: 3

2 1

1 1 1

答案 9 :(得分:-1)

我制作了一个小程序来帮助解决一个问题。就个人而言,我认为最好的是确定性的数学解决方案,但是现在我甚至没有思考如何实施它的咖啡因。 =)

相反,我选择了SAR方法。止损和反转是一种用于股票交易的技术(http://daytrading.about.com/od/stou/g/SAR.htm),并且大量用于以最小的推理计算最优曲线。 Wikipedia entry for parabolical SAR是这样的:

  

'抛物线SAR几乎是针对每种趋势独立计算的   在价格。当价格处于上升趋势时,特区出现在下方   价格并向上收敛。同样,在   下降趋势,特区高于价格,收敛   向下“。

我根据你的问题调整了它。我从你的系列中的随机值开始。然后代码进入有限数量的迭代。

我从系列堆栈中选择另一个随机值。 如果新值加上堆栈总和低于目标,则值已添加;如果优越,那么减少。 我可以继续我想要的,直到我满足条件(堆栈总和=目标),或者如果循环找不到有效的解决方案则中止。 如果成功,我会记录堆栈和迭代次数。然后我重做一切。

下面是一个非常粗略的代码。请原谅hastiness。哦,它在C#中。 =)

同样,它并不能保证你会获得最佳路径;这是一种蛮力的方法。它可以精炼;例如,检测目标命中是否存在完美匹配。

 public static class SAR
 {
    //I'm considering Optimal as the smallest signature (number of members).
    // Once set, all future signatures must be same or smaller.

    private static Random _seed = new Random();

    private static List<int> _domain = new List<int>() { 100, 80, 66, 24, 4, 2, 1 };

    public static void SetDomain(string domain)
    {
        _domain = domain.Split(',').ToList<string>().ConvertAll<int>(a => Convert.ToInt32(a));
        _domain.Sort();
    }

    public static void FindOptimalSAR(int value)
    {
        // I'll skip some obvious tests. For example:
        //   If there is no odd number in domain, then
        //   it's impossible to find a path to an odd
        //   value.

        //Determining a max path run. If the count goes
        //   over this, it's useless to continue.
        int _maxCycle = 10;

        //Determining a maximum number of runs.
        int _maxRun = 1000000;
        int _run = 0;

        int _domainCount = _domain.Count;

        List<int> _currentOptimalSig = new List<int>();
        List<String> _currentOptimalOps = new List<string>();
        do
        {

            List<int> currSig = new List<int>();
            List<string> currOps = new List<string>();

            int _cycle = 0;
            int _cycleTot = 0;
            bool _OptimalFound = false;

            do
            {
                int _cursor = _seed.Next(_domainCount);

                currSig.Add(_cursor);

                if (_cycleTot < value)
                {
                    currOps.Add("+");
                    _cycleTot += _domain[_cursor];
                }
                else
                {
                    // Your situation doesn't allow for negative
                    // numbers. Otherwise, just enable the two following lines.
                    // currOps.Add("-");
                    // _cycleTot -= _domain[_cursor];
                }

                if (_cycleTot == value)
                {
                    _OptimalFound = true;
                    break;
                }

                _cycle++;
            } while (_cycle < _maxCycle);

            if (_OptimalFound)
            {
                _maxCycle = _cycle;

                _currentOptimalOps = currOps;
                _currentOptimalSig = currSig;

                Console.Write("Optimal found: ");

                for (int i = 0; i < currSig.Count; i++)
                {
                    Console.Write(currOps[i]);
                    Console.Write(_domain[currSig[i]]);
                }

                Console.WriteLine(".");
            }

            _run++;

        } while (_run < _maxRun);
    }
}

这是来电者:

        String _Domain = "100, 80, 66, 25, 4, 2, 1";

        SAR.SetDomain(_Domain);

        Console.WriteLine("SAR for Domain {" + _Domain + "}");
        do
        {
            Console.Write("Input target value: ");
            int _parm = (Convert.ToInt32(Console.ReadLine()));

            SAR.FindOptimalSAR(_parm);
            Console.WriteLine("Done.");

        } while (true);

对于一些目标进行100k迭代后,这是我的结果,给出了一个稍微修改过的系列(为了测试目的,我将24切换为24):

SAR for Domain {100, 80, 66, 24, 4, 2, 1}
Input target value: 50
Optimal found: +24+24+2.
Done.
Input target value: 29
Optimal found: +4+1+24.
Done.
Input target value: 75
Optimal found: +2+2+1+66+4.
Optimal found: +4+66+4+1.
Done.

现在使用原始系列:

SAR for Domain {100, 80, 66, 25, 4, 2, 1}
Input target value: 50
Optimal found: +25+25.
Done.
Input target value: 75
Optimal found: +25+25+25.
Done.
Input target value: 512
Optimal found: +80+80+66+100+1+80+25+80.
Optimal found: +66+100+80+100+100+66.
Done.
Input target value: 1024
Optimal found: +100+1+80+80+100+2+100+2+2+2+25+2+100+66+25+66+100+80+25+66.
Optimal found: +4+25+100+80+100+1+80+1+100+4+2+1+100+1+100+100+100+25+100.
Optimal found: +80+80+25+1+100+66+80+80+80+100+25+66+66+4+100+4+1+66.
Optimal found: +1+100+100+100+2+66+25+100+66+100+80+4+100+80+100.
Optimal found: +66+100+100+100+100+100+100+100+66+66+25+1+100.
Optimal found: +100+66+80+66+100+66+80+66+100+100+100+100.
Done.

缺点:值得再次提及:此算法不保证您会找到最佳值。它使蛮力近似。

优点:快。 100k迭代最初可能看起来很多,但算法在检测到越来越多的优化路径后开始忽略长路径,因为它减少了允许的最大循环次数。