[英]Java programming question, issue with logic
最近我遇到了一个编程问题,例如,给定一个总数和一个输入 k 找到我们可以从 1 到 k 的数字达到总数的方法总数。
例如:总计 = 5,k = 3
output 应该是 5
因为我们可以使用 1、2 和 3 以 5 种方式达到 5,如下所示
1+1+1+1+1
1+1+1+2
1+2+2
1+1+3
2 + 3
我想出了下面的逻辑,但它并没有完全起作用,因为我没有做回溯(我想),我不知道该怎么做
private static int totalways(int total, int k) {
List<Integer> arr = new ArrayList();
for (int i=0; i<total; i++) {
arr.add(1);
}
int count = 1;
boolean breakLoop = true;
while (breakLoop) {
int last = arr.size()-1;
for (int i=last; i>=1; i--) {
if (arr.get(i) + arr.get(i-1) <= k) {
count++;
int sum = arr.get(i) + arr.get(i-1);
arr.remove(i-1);
arr.remove(i-1);
arr.add(sum);
}
}
if (arr.size() == 2){
breakLoop = false;
}
}
return count;
}
任何帮助表示赞赏。
这是一个可以通过动态规划轻松解决的经典问题。 另请参阅此类似问题: https://en.wikipedia.org/wiki/Change-making_problem
第一个观察结果是,当您尝试使用不超过k
的数字编写total
时,您可以使用k
或不使用k
。
如果您使用k
,那么您仍然必须使用不超过 k 的数字进行total - k
k
。 如果您不使用k
,那么您实际上是在total
k-1
。
如果我们将 c[total][k] 称为total
达到k
的方法的数量,那么我们的观察给出了一个公式: c[total][k] = c[total-k][k] + c[total][k-1]
。
编辑:如果k <= total
这个公式是真的。 如果k > total
,则c[total][k] = c[total][k-1]
。
我们还可以观察到,对于k
的所有值, c[0][k] = 1
,对于任何total > 0
的c[total][0] = 0
0。
编写一个简单的递归程序来使用我们的递归公式将是可怕的; 我们最终会得到指数级的复杂性,因为对于每次调用,我们都需要进行两次递归调用。
相反,我们可以在动态规划算法中使用我们的公式,只需用结果填充二维数组c[][]
:
int[][] c = new int[total+1][k+1];
for (int n = 1; n <= total; ++n)
{
c[n][0] = 0;
}
for (int j = 0; j <= k; ++j)
{
c[0][j] = 1;
}
for (int n = 1; n <= total; ++n)
{
int maxj = (k <= n) ? k : n; // maxj = min(k,n)
for (int j = 1; j <= maxj; ++j) // case j <= n
{
c[n][j] = c[n-j][j] + c[n][j-1];
}
for (int j = maxj + 1; j <= k; ++j) // case j > n
{
c[n][j] = c[n][j-1];
}
}
return c[total][k];
编辑:考虑到案例k > total
,根据评论
以你的例子,total = 5, k = 3,问题是找到一个 function "f(k, total)" 它将尝试从 1 到 k 的所有值 "v" 总和为 "total" 减去“v”。
f(3, 5) then does:
"f" tries 1 and must now sum to 4 i.e. calls f(3, 4)
"f" tries 2 and must now sum to 3 i.e. calls f(3, 3)
"f" tries 3 and must now sum to 2 i.e. calls f(3, 2)
请注意 function f 调用自身。 这称为递归调用。 当您需要回溯时,递归调用通常是简单的解决方案。
这将生成一个调用树,如:
f(3, 5) {}
f(3, 4) {1}
f(3, 3) {1, 1}
f(3, 2) {1, 1, 1}
f(3, 1) {1, 1, 1, 1}
f(3, 0) {1, 1, 1, 1, 1} *
f(3, 0) {1, 1, 1, 2} *
f(3, 1) {1, 1, 2}
f(3, 0) {1, 1, 2, 1} *
f(3, 0) {1, 1, 3}
...
当“total”参数为 0 时,调用过程停止。
此解决方案生成相同的组合 {1,1,1,2}, {1,1,2,1}...要解决此问题,您可以修改 f 逻辑,以便您永远不会尝试高于“v”的值你的父母来电者尝试了什么。 您的 function 逻辑将是:
f(k, total) {
...
for v in 1 to min(k, total) {
...
f(v, total - v)
}
}
新的完整调用树将是:
f(3, 5) {}
f(1, 4) {1}
f(1, 3) {1, 1}
f(1, 2) {1, 1, 1}
f(1, 1) {1, 1, 1, 1}
f(1, 0) {1, 1, 1, 1, 1} *
f(2, 3) {2}
f(1, 2) {2, 1}
f(1, 1) {2, 1, 1}
f(1, 0) {2, 1, 1, 1} *
f(2, 1) {2, 2}
f(1, 0) {2, 2, 1} *
f(3, 2) {3}
f(1, 1) {3, 1}
f(1, 0) {3, 1, 1} *
f(2, 0) {3, 2} *
您现在要做的就是在总数为 0 时累积找到的解决方案。
为此,您将需要某种类型的堆栈,您将在其中添加当前解决方案。
void f(int k, int total) {
if (total == 0) {
System.err.println("add a copy of the current stack to your solutions.");
return;
}
for (int v = 1; v <= Math.min(k, total); ++v) {
f(v, total - v);
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.