我对组合总和的动态编程解决方案有点困惑,您会得到一个数字列表和一个目标总数,并且您想要计算总计达到此目标总和的方式数量。数字可以多次重复使用。我对内循环和外循环感到困惑,他们是否可以互换。有些人可以解释以下两者之间的区别,在什么情况下我们会使用一个而不是另一个,或者它们是相同的。
int [] counts = new int[total];
counts[0] = 1;
// (1)
for(int i = 0; i <= total; i++) {
for(int j = 0; j < nums.length; j++) {
if(i >= nums[j])
counts[i] += counts[i - nums[j]];
}
}
// (2)
for(int j = 0; j < nums.length; j++)
for(int i = nums[j]; i <= total; i++) {
counts[i] += counts[i - nums[j]];
}
}
答案 0 :(得分:2)
这两个版本确实不同,产生了不同的结果。
我会在下面的所有示例中使用nums = {2, 3}
。
版本1查找nums
中总和为total
的元素与排序的组合数。它通过迭代所有“小计”并计算有多少组合具有正确的总和来实现,但它不跟踪元素。例如,5的计数将为2.这是使用第一个元素(值为2)并在nums[3]
中找到1个组合而第二个元素(值3)与1个组合的另一个组合的结果在nums[2]
。您应该注意两种组合使用单个2和单个3,但它们代表2 不同的有序列表 [2, 3]
&amp; [3, 2]
。
版本2查找的组合数量,但不排序来自nums
的元素,其总和为total
。它通过计算有多少组合具有正确的总和(毛发每个小计)来实现,但与版本1相反,它在移动到下一个元素之前完全“使用”每个元素,从而避免了同一组的不同排序。当使用第一个元素(2)计算小计时,所有计数最初将为0(除了0和前哨),任何偶数小计将获得新的计数1.当使用下一个元素时,就好像它是在它之后所有2都已在群组中,因此,与版本1相反,只计算[2, 3]
,而不是[3, 2]
。
顺便说一句,nums
中元素的顺序不会影响结果,正如所解释的逻辑所理解的那样。
答案 1 :(得分:0)
动态编程的工作原理是填写表中的条目,假设表中的先前条目已完全完成。
在这种情况下,我们counts[i]
依赖于counts[i - nums[j]];
j
中的每个条目nums
。
在此代码段中
// (1)
for(int i = 0; i < total; i++) {
for(int j = 0; j < nums.length; j++) {
if(i >= nums[j])
counts1[i] += counts1[i - nums[j]];
}
}
我们按顺序从0
到total
按顺序填写表格。这是外循环的动作。内部循环遍历我们不同的nums
并根据之前的值更新表中的当前条目,这些值都是假设已完成。
现在看一下这个片段
// (2)
for(int j = 0; j < nums.length; j++){
for(int i = nums[j]; i < total; i++) {
counts2[i] += counts2[i - nums[j]];
}
}
在这里,我们将遍历不同计数列表并更新总计。这打破了动态编程的概念。在我们完成表格之前,我们的所有参赛作品都不能被认为是完整的。
它们是一样的吗?答案是否定的。以下代码说明了这一事实:
public class dyn {
public static void main(String[] args) {
int total = 50;
int[] nums = new int[]{1, 5, 10};
int [] counts1 = new int[total];
int [] counts2 = new int[total];
counts1[0] = 1;
counts2[0] = 1;
// (1)
for(int i = 0; i < total; i++) {
for(int j = 0; j < nums.length; j++) {
if(i >= nums[j])
counts1[i] += counts1[i - nums[j]];
}
}
// (2)
for(int j = 0; j < nums.length; j++){
for(int i = nums[j]; i < total; i++) {
counts2[i] += counts2[i - nums[j]];
}
}
for(int k = 0; k < total; k++){
System.out.print(counts1[k] + ",");
}
System.out.println("");
for(int k = 0; k < total; k++){
System.out.print(counts2[k] + ",");
}
}
}
这将输出2个不同的列表。
它们不同,因为我们正在使用表格中较早的不完整信息更新我们的counts[i]
。 counts[6]
假设您拥有counts[5]
和counts[1]
的条目,而counts[4]
和counts[3]
的条目依次为counts[2]
,counts[0]
,int[] nums = new int[]{3, 6}
,和counts[3+6]
。因此,每个条目都依赖于(在最坏的情况下所有)表中的先前条目。
<强>附录:强>
有趣(可能是显而易见的)旁注:
这两种方法产生相同的列表,直到nums中条目的最小成对和。
为什么?
这是来自先前条目的信息变得不完整(相对于第一个循环)。也就是说,如果我们有count[3]
,则count[6]
将无法正确计算,因为其中之一
$'import sys,itertools\nfor x in itertools.izip_longest(*[iter(sys.stdin)]*50): print(",".join(x).replace("\\n",""))\n'
不正确或S= [1, 5, 13, 21]
def push(S):
return S
def isEmpty(S):
if S = []:
print ("Empty String")
else:
print ("String is not empty")
def Top(S):
if S = []:
print ("Empty String")
else:
return S[0]
def nextToTop(S):
if S = []:
print ("String is not empty")
else:
return S[1]
print(push(S))
print(isEmpty(S))
print(Top(S))
print(nextToTop(S))
不会与使用第一个循环获得的结果对齐,具体取决于我们已经完成的计算阶段。
答案 2 :(得分:0)
鉴于对我之前回答的批评,我认为我会采取更多的数学方法。
在@Amit的回答中,我将在示例中使用nums = {2, 3}
。
第一个循环计算
S(n)= S(n-3)+ S(n-2)
或者,更一般地说,对于某些集合{x_1, x_2, x_3, ... ,x_k}
:
S(n)= S(n-x_1)+ S(n-x_2)+ ... + S(n-x_k)
应该很清楚,每个S(n)
都依赖于(可能是所有)以前的值,因此我们必须从0
开始,并将表格向上填充到我们想要的total
。< / p>
第二个循环使用以下定义计算重复S_2(n):
S_1(n)= S_1(n-2)
S_2(n)= S_1(n)+ S_2(n-3)
更一般地说,对于某些集合{x_1, x_2, x_3, ... ,x_k}
:
S_1(n)= S_1(n-x_1)
S_2(n)= S_1(n)+ S_2(n-x_2)
...
S_k(n)= S_ {k-1}(n)+ S_k(n-x_k)
此序列中的每个条目都与第一个循环中的条目类似;它取决于之前的条目。但与第一个循环不同,它也依赖于早期的序列。
或许更具体地说:
S_2不仅取决于(可能全部)先前的S_2条目,还取决于先前的S_1条目。
因此,当我们想要计算第一次重复时,我们从0开始计算每个条目,为nums
中的每个数字计算。
当我们想要计算第二次重复时,我们一次计算一次中间重复,每次都将结果存储在counts
中。
这两次重复计算的是什么?正如@Amit的回答所解释的那样,他们计算总和为total
的组合数,包括和不保留顺序。很容易理解为什么,再次使用我们的nums = {2, 3}
:
请注意我使用列表一词来表示有序的内容,并使用设置一词来表示无序的内容。
我使用附加表示添加前者,添加表示添加后者。
有多少列表的数字加到2,
以及有多少人添加到3,
您可以追加每个前列表一个3,后者列表中的每一个<2>。
因此(多少加5)=(多少加3)+(多少加2)
这是我们的第一次复发。
第二次重复,
只有2
的集添加到5(0)
只有2
和3
的集添加到2(1)
您可以获取所有第一个号码,然后您可以添加 3到第二个号码中的所有集。
请注意“设置只是2
的”是一个特殊情况,只有2
和{{设置 1}}的”。 “设置只有3
和2
的”取决于“设置仅{{1} “就像我们的复发一样!”
以下递归函数计算第一个循环的值,示例值为3
和2
。
3
以下一组递归函数计算第二个循环的值,示例值为2
和public static int r(int n){
if(n < 0)
return 0;
if(n == 0)
return 1;
return r(n-2) + r(n-3);
}
。
3
我已将它们检查到2
并且它们似乎是正确的。