组合和内循环和外循环的动态编程是否可以互换?

时间:2016-10-24 18:42:11

标签: java algorithm dynamic-programming

我对组合总和的动态编程解决方案有点困惑,您会得到一个数字列表和一个目标总数,并且您想要计算总计达到此目标总和的方式数量。数字可以多次重复使用。我对内循环和外循环感到困惑,他们是否可以互换。有些人可以解释以下两者之间的区别,在什么情况下我们会使用一个而不是另一个,或者它们是相同的。

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]];
   }
}

3 个答案:

答案 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]];
       }
    }

我们按顺序从0total 按顺序填写表格。这是外循环的动作。内部循环遍历我们不同的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,

我问你

  • 多少加5?

您可以追加每个前列表一个3,后者列表中的每一个<2>。

因此(多少加5)=(多少加3)+(多少加2)

这是我们的第一次复发。

第二次重复,

如果您知道

  • 只有2添加到5(0)

  • 只有23添加到2(1)

您可以获取所有第一个号码,然后您可以添加 3到第二个号码中的所有

请注意“设置只是2的”是一个特殊情况,只有2和{{设置 1}}的”。 “设置只有32的”取决于设置仅{{1} “就像我们的复发一样!”

用java

编写的递归函数

以下递归函数计算第一个循环的值,示例值为32

3

以下一组递归函数计算第二个循环的值,示例值为2public static int r(int n){ if(n < 0) return 0; if(n == 0) return 1; return r(n-2) + r(n-3); }

3

我已将它们检查到2并且它们似乎是正确的。