三角形中的路径数

时间:2015-04-13 00:53:40

标签: algorithm recursion dynamic-programming combinatorics

我最近遇到了这个问题的一个更难的变化,但意识到我无法为这个非常简单的情况生成解决方案。我搜索了Stack Overflow,但找不到之前回答此问题的资源。

您将获得一个三角形ABC,您必须计算从“A”开始并以“A”结束的特定长度的路径数。假设我们的函数f(3)被调用,它必须返回长度为3的路径数,它以A:2开始和结束(ABA,ACA)。

我在制定优雅的解决方案时遇到了麻烦。现在,我已经编写了一个生成所有可能路径的解决方案,但是对于更大的长度,程序太慢了。我知道必须有一个很好的动态编程解决方案,它重用我们之前计算过的序列,但我无法弄明白。所有人都非常感谢。

我的愚蠢代码:

def paths(n,sequence):
    t = ['A','B','C']
    if len(sequence) < n:
        for node in set(t) - set(sequence[-1]):
            paths(n,sequence+node)
    else:
        if sequence[0] == 'A' and sequence[-1] == 'A':
            print sequence

5 个答案:

答案 0 :(得分:3)

PA(n)成为从A回到A的路径数,恰好是n步。 设P!A(n)为恰好n步的从B(或C)到A的路径数。

然后:

PA(1) = 1
PA(n) = 2 * P!A(n - 1)

P!A(1) = 0
P!A(2) = 1
P!A(n) = P!A(n - 1) + PA(n - 1)
       = P!A(n - 1) + 2 * P!A(n - 2) (for n > 2) (substituting for PA(n-1))

我们可以解析P!A的差分方程,正如我们对Fibonacci所做的那样,注意(-1)^ n和2 ^ n都是差分方程的解,然后找到系数a,b等P!A(n)= a * 2 ^ n + b *( - 1)^ n。

我们得到方程P!A(n)= 2 ^ n / 6 +( - 1)^ n / 3,PA(n)为2 ^(n-1)/ 3 - 2( - 1)^ N / 3。

这给了我们代码:

def PA(n):
    return (pow(2, n-1) + 2*pow(-1, n-1)) / 3

for n in xrange(1, 30):
    print n, PA(n)

提供输出:

1 1
2 0
3 2
4 2
5 6
6 10
7 22
8 42
9 86
10 170
11 342
12 682
13 1366
14 2730
15 5462
16 10922
17 21846
18 43690
19 87382
20 174762
21 349526
22 699050
23 1398102
24 2796202
25 5592406
26 11184810
27 22369622
28 44739242
29 89478486

答案 1 :(得分:2)

我的方法是这样的:

定义DP(l,end)=路径数在end结束并且长度为l 然后DP(l,'A')= DP(l-1,'B')+ DP(l-1,'C'),类似于DP(l,'B')和DP(l,'C') )

然后对于基本情况即。 l = 1 我检查结尾是不是'A',然后我返回0,否则返回1,这样所有较大的状态只计算那些'A'开头

答案只是调用DP(n,'A'),其中n是长度

下面是C ++中的示例代码,你可以用3调用它,它给你2个答案;用5来称呼它给你6个答案: ABCBA,ACBCA,ABABA,ACACA,ABACA,ACABA

#include <bits/stdc++.h>
using namespace std;

int dp[500][500], n;

int DP(int l, int end){
	if(l<=0) return 0;
	if(l==1){
		if(end != 'A') return 0;
		return 1;
	}
	if(dp[l][end] != -1) return dp[l][end];
	
	if(end == 'A') return dp[l][end] = DP(l-1, 'B') + DP(l-1, 'C');
	else if(end == 'B') return dp[l][end] = DP(l-1, 'A') + DP(l-1, 'C');
	else return dp[l][end] = DP(l-1, 'A') + DP(l-1, 'B');
}

int main() {
	memset(dp,-1,sizeof(dp));
	scanf("%d", &n);
	
	printf("%d\n", DP(n, 'A'));
	return 0;
}

<强> EDITED 要回答以下OP的评论:

首先,DP(动态编程)总是处于状态。

请记住,我们的状态是DP(l,end),表示长度为l且结束于end的路径数。因此,要使用编程实现状态,我们通常使用数组,因此DP [500] [500]没什么特别的,但存储状态DP(l,end)的空间对于所有可能的lend(这就是为什么我说如果你需要更大的长度,改变数组的大小)

但是你可能会问,我理解l的第一个维度,500表示l可以大到500,但是第二个维度怎么样?我只需要'A','B','C',为什么使用500呢?

这是另一个技巧(C / C ++),默认情况下char类型确实可以用作int类型,该值等于其ASCII数字。我当然不记得ASCII表,但我知道大约300个就足以代表所有的ASCII字符,包括A(65)​​,B(66),{{1} }(67)

所以我只是声明任何足够大的尺寸来表示第二维中的'A','B','C'(这意味着实际上100就足够了,但我只是不认为那么多,并宣称500为就订单而言,它们几乎相同)

所以你问DP [3] [1]意味着什么,它没有任何意义,因为当它是1时我不需要/计算第二个维度。(或者可以认为状态dp(3,1)确实存在在我们的问题中没有任何实际意义)

事实上,我总是使用65,66,67。 所以DP [3] [65]表示长度为3的路径数,以char(65)='A'结束

答案 2 :(得分:2)

技巧不是试图生成所有可能的序列。它们的数量呈指数级增长,因此所需的内存太大了。

相反,让f(n)为长度n开头和结尾A的序列数,让g(n)为长度n的序列数以A开头,但以B结尾。要开始工作,请清楚f(1) = 1g(1) = 0。对于n > 1,我们有f(n) = 2g(n - 1),因为倒数第二个字母为BC,每个字母的数量相等。我们还有g(n) = f(n - 1) + g(n - 1),因为如果序列结束时开始A并结束B,则倒数第二个字母为AC

这些规则允许您使用memoization快速计算数字。

答案 3 :(得分:2)

对于给定的三角形和更一般的图形,您可以比其他人发布的动态编程/递归解决方案做得更好。每当您尝试计算(可能是定向的)图形中的行走次数时,您可以根据传输矩阵的幂的条目来表达它。令M为矩阵,其条目m [i] [j]是从顶点i到顶点j的长度为1的路径的数量。对于三角形,传递矩阵是

0 1 1
1 0 1.
1 1 0

然后M ^ n是一个矩阵,其i,j项是从顶点i到顶点j的长度为n的路径数。如果A对应于顶点1,则需要M ^ n的1,1项。

根据长度为n-1的路径计算长度为n的路径的动态编程和递归相当于用n次乘法计算M ^ n,M * M * M * ... * M,它们可以足够快。但是,如果要计算M ^ 100,而不是进行100次乘法,则可以使用repeated squaring:计算M,M ^ 2,M ^ 4,M ^ 8,M ^ 16,M ^ 32,M ^ 64,然后M ^ 64 * M ^ 32 * M ^ 4。对于较大的指数,乘法的数量约为c log_2(指数)。

而不是使用长度为n的路径由长度为n-1的路径构成,然后是长度为1的步长,而使用长度为n的路径由长度为k的路径构成一条长度为nk的路径。

答案 4 :(得分:0)

我们可以使用for循环来解决这个问题,尽管Anonymous描述了一个封闭的表单。

function f(n){
  var as = 0, abcs = 1;

  for (n=n-3; n>0; n--){
    as = abcs - as;
    abcs *= 2;
  }

  return 2*(abcs - as);
}

原因如下:

Look at one strand of the decision tree (the other one is symmetrical):

                                        A

                  B                                            C...
     A                        C
B         C               A        B
A    C    A    B          B   C    A   C
B C  A B  B C  A C        A C A B  B C A B


Num A's     Num ABC's (starting with first B on the left)
0            1
1 (1-0)      2
1 (2-1)      4
3 (4-1)      8
5 (8-3)      16
11 (16-5)    32

Cleary, we can't use the strands that end with the A's...