所以这就是问题所在:
鉴于input = [100 80 66 25 4 2 1]
,我需要找到最佳组合给我50。
看看这个,最好的是25 + 25 = 50,所以我需要数组中的2个元素。
其他组合包括25+4+4+4+4+4+4+1
和25+4+4+4+4+4+2+2+1
等等
我需要找到所有可能性,这些可能性给出了我想要的值的总和。
编辑:以及最好的可能性(一个条款数量最少的一个)
这是我到目前为止所做的事情: 首先构建一个新的数组(循环的简单循环遍历所有元素并存储在新的临时数组中),检查高于我的数组的所有元素(因此对于输入50,元素100,80,66更高,所以丢弃它们然后我的新阵列是[25 4 2 1])。然后,由此,我需要检查组合。
我做的第一件事是一个简单的if语句,检查是否有任何数组元素与我想要的数字完全匹配。所以,如果我想要50,我会检查阵列中是否有50,如果没有,我需要找到组合。
我的问题是,我不完全确定如何找到每一个组合。我一直在努力尝试提出一段时间的算法,但我总是最终陷入困境。
非常感谢任何帮助/提示。
PS - 我们可以假设数组总是按照从LARGEST到SMALLEST值的顺序排序。
答案 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迭代最初可能看起来很多,但算法在检测到越来越多的优化路径后开始忽略长路径,因为它减少了允许的最大循环次数。