1 超级楼梯
(1)问题描述
有一个超级楼梯共n级,刚开始的时候你在第一级,每次只能跨上一级或者跨上两级,要走上n级台阶,共有多少种做法?
(2)问题分析与解答
当走上第一级时,你只能一步走上去。这时只有一种走法;当走上第二级时,你可以一级一级走上去,也可以一次走两级。这时有两种走法;当走上第三级时,你可以一级一级走上去,也可以先走两级,再走一级,还可以先走两级,再走一级。这时共有3种走法;当走上第四级时,正着分析就有些繁琐了,这时我们可以倒着分析。当我们走最后一步时,可以有两种方法,即一种是由第三级走一级走上去,另一种是由第二级一次性走两级走上去,那么求走到第四级的走法种数就可以转换为走到第三级的走法种数加上走到第二级的走法种数之和。同理,当我们要走到第n级,最后一步可以由第n-1级一步走上去,也可以由第n-2级一次跨两级走上去那么求走到第n级的走法种数就可以转换为走到第n-2级的走法种数加上走到第n-1级的走法种数之和。设f(n)为走到第n级的走法种数,那么则有如下递推表达式:
f
(
n
)
=
{
1
,
n
=
1
2
,
n
=
2
f
(
n
−
2
)
+
f
(
n
−
1
)
,
n
>
2
f(n)=\begin{cases}1,n=1\\2,n=2\\f(n-2)+f(n-1),n>2\end{cases}
f(n)=⎩
⎨
⎧1,n=12,n=2f(n−2)+f(n−1),n>2
注意n为正整数。
我们首先可以使用dfs方法来实现该段程序,如下:
import java.util.Scanner;
public class ChaoJiLouTi {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n;
System.out.print("请输入要走到的台阶数:");
n = scanner.nextInt();
System.out.print("可以到达该台阶的走法为:" + dfs(n));
scanner.close();
}
//使用dfs的实现方式
public static int dfs(int n) {
if (n == 1) {//走到第1级台阶共有1种走法
return 1;
}
if (n == 2) {//走到第2级台阶共有2种走法
return 2;
}
return dfs(n - 2) + dfs(n - 1);//走到第n级台阶共有f(n-2)+f(n-1)种方法
}
}
测试如下:
请输入要走到的台阶数:4
可以到达该台阶的走法为:5
但是我们仔细分析就会发现,使用dfs方法进行递归过程中会存在大量的重复计算,严重影响着程序的时间效率,下面一求f(5)时进行举例,画出它的递归树如下图所示:
如上图所示,根据递归程序可知,当我们求解f(5)时,会先求解f(3),根据f(1)=1和f(2)=2求解出f(3)=1+2=3后会进行f(4)的求解。当进行f(4)的求解时,会进行f(2)和f(3)的求解。此时我们就会发现f(3)其实我们已经求过了,若要再次求解则会影响程序的时间效率,于是为了解决这个问题,我们引申出该题目的递推解法。
我们以将一定范围内的走法种数求出保存在数组中,当需要的时候再进行调用,这样就解决了重复求取的问题,可得出以下递归算法:
import java.util.Scanner;
public class ChaoJiLouTi_DiTui1 {
static final int MAX = 50;
static int[] f = new int[MAX];// 保存到每级台阶的走法种数的数组f
public static void main(String[] args) {
//使用递推实现
Scanner scanner = new Scanner(System.in);
f[0] = 1;// 第一级台阶的走法数为1
f[1] = 2;// 第二级台阶的走法数为2
int n;// n代表要走到的台阶数
// 将到每级台阶的走法种数算出存在数组f中
for (int i = 2; i < MAX; i++) {
f[i] = f[i - 2] + f[i - 1];
}
System.out.print("请输入要走到的台阶数:");
n = scanner.nextInt();
System.out.print("可以到达该台阶的走法为:" + f[n - 1]);
}
}
测试如下:
请输入要走到的台阶数:4
可以到达该台阶的走法为:5
当然,此种方法也会消耗一定的内存,于是我们得到优化后的递归算法如下:
import java.util.Scanner;
public class ChaoJiLouTi_DiTui2 {
public static void main(String[] args) {
// 使用优化后的递归实现
Scanner scanner = new Scanner(System.in);
int f1 = 1, f2 = 2, n;// f1,f2分别为递归变量,n为要走到的台阶数
System.out.print("请输入要走到的台阶数:");
n = scanner.nextInt();
int m = n % 2 == 0 ? n / 2 - 1 : n / 2;// 以m来记录循环次数(若n为奇数,要循环n/2次;若n为偶数,要循环n/2-1次)
for (int i = 1; i <= m; i++) {
f1 = f1 + f2;// 分别求取第3,5,7,9……级台阶的走法种数
f2 = f2 + f1;// 分别求取第4,6,8,10……级台阶的走法种数
}
if (n % 2 == 1) {// n对2取模为1,输出f1
System.out.println("可以到达该台阶的走法为:" + f1);
} else {// n对2取模为0,输出f2
System.out.println("可以到达该台阶的走法为:" + f2);
}
scanner.close();
}
}
测试如下:
请输入要走到的台阶数:4
可以到达该台阶的走法为:5
2 骨牌铺方格
(1)问题描述
在2ⅹn的正方形方格中,用若干个1ⅹ2的骨牌铺满方格,要求输入n,输出铺设方案的总数。例如当n=3时,为2ⅹ3方格,此时骨牌的铺设方案由3种,如下图:
(2)问题分析与解答
当要铺满2ⅹ1的正方形方格时,有1种铺设方案,如下图所示:
当要铺满2ⅹ2的正方形方格时,有2种铺设方案,如下图所示:
当要铺满2ⅹ3的正方形方格时,有3种铺设方案,如题目中的图形所示。
当要铺满2ⅹ4的正方形方格时,正着分析就显得有些繁琐了,这时我们可以进行倒着分析来寻找铺满2ⅹ4个方格和铺满2ⅹ3、2ⅹ2、2ⅹ1的它们铺设种数的关系,分析如下:
①若此时已经铺满2ⅹ3的正方形方格,还剩下1列方格,铺满剩下的这1列方格有1种铺设方法,如下图所示:
由此种情况到铺满整个方格共有3(铺满2ⅹ3的正方形方格的铺设方案种数)ⅹ1(铺满剩下的这1列方格的铺设方案种数)=3种方案。
②若此时已经铺满2ⅹ2的正方形方格,还剩下2列方格,铺满剩下的这2列方格有2种铺设方法,如下图所示:
但是第1种铺设方案在第1种情况我们已经计算过了,因此由此种情况到铺满整个方格共有2(铺满2ⅹ2的正方形方格的铺设方案种数)ⅹ1(铺满剩下的这2列方格的铺设方案种数)=2种方案。
③若此时已经铺满2ⅹ1的正方形方格,还剩下3列方格,铺满剩下的这3列方格有3种铺设方法,如下图所示:
但是我们发现这3种铺设方法我们在第1、2种情况我们都已经计算过了,因此由此种情况我们舍弃。
最终我们计算得出铺满2ⅹ4的正方形方格共有3+2=5种铺设方案。同时我们发现由于我们只能使用1ⅹ2的骨牌,因此当我们要铺满2ⅹ4的方格从后往前分析时只和前两种方案有关,再向前分析必然全是重复的结果。而且铺满2ⅹ4的方格的铺设种数是前两种方案的种数之和。
……
当要铺满2ⅹn(n为一个较大的数)的正方形方格时,由以上分析,可知铺满2ⅹn的正方形方格的铺设方案种数=铺满2ⅹ(n-2)的正方形方格的铺设方案种数+铺满2ⅹ(n-1)的正方形方格的铺设方案种数。
综上所述,设f(n)为铺满2ⅹn的正方形方格的铺设方案种数,则f(n)满足以下递推表达式:
f
(
n
)
=
{
1
,
n
=
1
2
,
n
=
2
f
(
n
−
2
)
+
f
(
n
−
1
)
,
n
>
2
f(n)=\begin{cases}1,n=1\\2,n=2\\f(n-2)+f(n-1),n>2\end{cases}
f(n)=⎩
⎨
⎧1,n=12,n=2f(n−2)+f(n−1),n>2
由以上递推表达式,我们可以编写解题的递推程序如下:
import java.util.Scanner;
public class GuPaiPuFangGe_DiTui {
public static void main(String[] args) {
//使用递推实现
Scanner scanner = new Scanner(System.in);
int f1 = 1, f2 = 2, n, m;//f1,f2分别为递推变量,n为题目中的n,m为循环次数
System.out.print("请输入n:");
n = scanner.nextInt();
m = n % 2 != 0 ? n / 2 : n / 2 - 1;//以m来记录循环次数(若n为奇数,要循环n/2次;若n为偶数,要循环n/2-1次)
for (int i = 1; i <= m; i++) {
f1 = f1 + f2;//分别求取n=3、5、7、9……时的铺设种数
f2 = f2 + f1;//分别求取n=3、5、7、9……时的铺设种数
}
if (n % 2 == 1) {//n对2取模为1,输出f1
System.out.print("铺设骨牌的方案种数为:" + f1);
} else {//n对2取模为0,输出f2
System.out.print("铺设骨牌的方案种数为:" + f2);
}
scanner.close();
}
}
测试如下:
请输入n:4
铺设骨牌的方案种数为:5
下面我们对题目做相应的修改,再来进行分析。
变式1
(1)问题描述
在2ⅹn的正方形方格中,用若干个1ⅹ2,2ⅹ2的骨牌铺满方格,要求输入n,输出铺设方案的总数。例如当n=4时,为2ⅹ3方格,此时骨牌的铺设方案由5种,如下图:
(2)问题分析与解答
当要铺满2ⅹ1的正方形方格时,有1种铺设方案,如下图所示:
当要铺满2ⅹ2的正方形方格时,有3种铺设方案,如下图所示:
当要铺满2ⅹ3的正方形方格时,有5种铺设方案,如题目中所示。
……
当要满2ⅹn(n为一个较大的数)的正方形方格时,我们还采用从后往前的分析方法,设f(n)为铺满n列方格的铺设方案种数,关于铺满2ⅹn的正方形方格的方案种数分析如下:
①若此时已经铺满2ⅹ(n-1)的正方形方格,还剩下1列方格,铺满剩下的这1列方格有1种铺设方法,如下图所示:
由此种情况到铺满整个方格共有f(n-1)(铺满2ⅹ(n-1)的正方形方格的铺设方案种数)ⅹ1(铺满剩下的这1列方格的铺设方案种数)=f(n-1)种方案。
②若此时已经铺满2ⅹ(n-2)的正方形方格,还剩下2列方格,铺满剩下的这1列方格有3种铺设方法,如下图所示:
但是第2种铺设方案在第1种情况我们已经计算过了,因此由此种情况到铺满整个方格共有f(n-2)(铺满2ⅹ(n-2)的正方形方格的铺设方案种数)ⅹ2(铺满剩下的这2列方格的铺设方案种数)=2f(n-2)种方案。
③若此时已经铺满2ⅹ(n-3)的正方形方格,还剩下3列方格,铺满剩下的这3列方格有5种铺设方法,如下图所示:
但是以上的每种铺设方案在前面2种情况我们都已经计算过了,因此此种情况我们舍弃。
由于我们只能用1ⅹ2,2ⅹ2的骨牌铺设方格,易知再往前分析必然全都是重复的情况。因此铺满2ⅹn的方格的方案种数f(n)=2f(n-2)+f(n-1)。由以上分析,可以得到以下递推表达式:
f
(
n
)
=
{
1
,
n
=
1
3
,
n
=
2
2
f
(
n
−
2
)
+
f
(
n
−
1
)
,
n
>
2
f(n)=\begin{cases}1,n=1\\3,n=2\\2f(n-2)+f(n-1),n>2\end{cases}
f(n)=⎩
⎨
⎧1,n=13,n=22f(n−2)+f(n−1),n>2
由以上递推表达式,我们可以编写解题的递推程序如下:
import java.util.Scanner;
public class GuPaiPuFangGe_DiTui {
public static void main(String[] args) {
//使用递推实现
Scanner scanner = new Scanner(System.in);
int f1 = 1, f2 = 3, n, m;//f1,f2分别为递推变量,n为题目中的n,m为循环次数
System.out.print("请输入n:");
n = scanner.nextInt();
m = n % 2 != 0 ? n / 2 : n / 2 - 1;//以m来记录循环次数(若n为奇数,要循环n/2次;若n为偶数,要循环n/2-1次)
for (int i = 1; i <= m; i++) {
f1 = 2*f1 + f2;//分别求取n=3、5、7、9……时的铺设种数
f2 = 2*f2 + f1;//分别求取n=3、5、7、9……时的铺设种数
}
if (n % 2 == 1) {//n对2取模为1,输出f1
System.out.print("铺设骨牌的方案种数为:" + f1);
} else {//n对2取模为0,输出f2
System.out.print("铺设骨牌的方案种数为:" + f2);
}
scanner.close();
}
}
测试如下:
请输入n:4
铺设骨牌的方案种数为:11
通过观察我们可以发现该程序与原题的解题程序只有f1、f2的初值(准确来说是只有f2发生了变化)和关于f1、f2的递推表达式发生了变化。
变式3
(1)问题描述
在2ⅹn的正方形方格中,用若干个3种的骨牌铺满方格,3种骨牌如下:
要求输入n,输出铺设方案的总数。例如当n=4时,为2ⅹ4方格,此时骨牌的铺设方案有10种,如下图:
(2)问题分析与解答
当要铺满2ⅹ1的正方形方格时,有1种铺设方案,如下图所示:
当要铺满2ⅹ2的正方形方格时,有2种铺设方案,如下图所示:
当要铺满2ⅹ3的正方形方格时,有5种铺设方案,如下图所示:
当要铺满2ⅹ4的正方形方格时,有10种铺设方案,如题目中所示。
……
当要满2ⅹn(n为一个较大的数)的正方形方格时,我们还采用从后往前的分析方法,设f(n)为铺满n列方格的铺设方案种数,关于铺满2ⅹn的正方形方格的方案种数分析如下:
①若此时已经铺满2ⅹ(n-1)的正方形方格,还剩下1列方格,铺满剩下的这1列方格有1种铺设方法,如下图所示:
由此种情况到铺满整个方格共有f(n-1)(铺满2ⅹ(n-1)的正方形方格的铺设方案种数)ⅹ1(铺满剩下的这1列方格的铺设方案种数)=f(n-1)种方案。
②若此时已经铺满2ⅹ(n-2)的正方形方格,还剩下2列方格,铺满剩下的这1列方格有2种铺设方法,如下图所示:
但是第1种铺设方案在第1种情况我们已经计算过了,因此由此种情况到铺满整个方格共有f(n-2)(铺满2ⅹ(n-2)的正方形方格的铺设方案种数)ⅹ1(铺满剩下的这2列方格的铺设方案种数)=f(n-2)种方案。
③若此时已经铺满2ⅹ(n-3)的正方形方格,还剩下3列方格,铺满剩下的这3列方格有5种铺设方法,如下图所示:
但是最后3种铺设方案在第1、2种情况我们已经计算过了,因此由此种情况到铺满整个方格共有f(n-3)(铺满2ⅹ(n-3)的正方形方格的铺设方案种数)ⅹ2(铺满剩下的这3列方格的铺设方案种数)=2f(n-3)种方案。
④若此时已经铺满2ⅹ(n-4)的正方形方格,还剩下4列方格,铺满剩下的这4列方格有10种铺设方法,如下图所示:
但是除了第2种铺设方案其它的铺设方案在第1、2、3种情况我们已经计算过了,因此由此种情况到铺满整个方格共有f(n-4)(铺满2ⅹ(n-4)的正方形方格的铺设方案种数)ⅹ1(铺满剩下的这4列方格的铺设方案种数)=f(n-4)种方案。
由于我们只能用题中所给的3种骨牌铺设方格,易知再往前分析必然全都是重复的情况。因此铺满2ⅹn的方格的方案种数f(n)=f(n-4)+2f(n-3)+f(n-2)+f(n-1)。由以上分析,可以得到以下递推表达式:
f
(
n
)
=
{
1
,
n
=
1
2
,
n
=
2
5
,
n
=
3
10
,
n
=
4
f
(
n
−
4
)
+
2
f
(
n
−
3
)
+
f
(
n
−
2
)
+
f
(
n
−
1
)
,
n
>
4
f(n)=\begin{cases}1,n=1\\2,n=2\\5,n=3\\10,n=4\\f(n-4)+2f(n-3)+f(n-2)+f(n-1),n>4\end{cases}
f(n)=⎩
⎨
⎧1,n=12,n=25,n=310,n=4f(n−4)+2f(n−3)+f(n−2)+f(n−1),n>4
由以上递推表达式,我们可以编写解题的递推程序如下:
import java.util.Scanner;
public class GuPaiPuFangGe_DiTui {
public static void main(String[] args) {
// 使用递推实现
Scanner scanner = new Scanner(System.in);
int f1 = 1, f2 = 2, f3 = 5, f4 = 10, n, m;// f1,f2,f3,f4分别为递推变量,n为题目中的n,m为循环次数
System.out.print("请输入n:");
n = scanner.nextInt();//
m = n % 4 != 0 ? n / 4 : n / 4 - 1;// 以m来记录循环次数(若n为奇数,要循环n/4次;若n为偶数,要循环n/4-1次)
for (int i = 1; i <= m; i++) {
f1 = f1 + 2 * f2 + f3 + f4;// 分别求取n=5、6、7、8……时的铺设种数
f2 = f2 + 2 * f3 + f4 + f1;// 分别求取n=9、10、11、12……时的铺设种数
f3 = f3 + 2 * f4 + f1 + f2;// 分别求取n=13、14、15、16……时的铺设种数
f4 = f4 + 2 * f1 + f2 + f3;// 分别求取n=17、18、19、20……时的铺设种数
}
if (n % 4 == 1) {// n对4取模为1,输出f1
System.out.print("铺设骨牌的方案种数为:" + f1);
} else if (n % 4 == 2) {// n对4取模为2,输出f2
System.out.print("铺设骨牌的方案种数为:" + f2);
} else if (n % 4 == 3) {// n对4取模为3,输出f3
System.out.print("铺设骨牌的方案种数为:" + f3);
} else {// n对4取模为0,输出f4
System.out.print("铺设骨牌的方案种数为:" + f4);
}
scanner.close();
}
}
测试如下:
请输入n:5
铺设骨牌的方案种数为:20
通过观察我们可以发现该程序与上面的解题程序也大同小异。只在控制循环次数、写循环表达式、控制输出时发生了变化。
综上所述,针对以上题目,我们需要首先需要认真分析题意,找到递推初量、递推前者和后者之间的关系,然后根据递推初量、递推前者和后者之间的关系写出递推表达式,最后再进行程序的编写(注意在写程序时要注意控制循环的次数,正确写出循环表达式并控制好输出),便可以解答出该类题目。