公园票价为5角。假设每位游客只持有两种币值的货币:5角、1元。
再假设持有5角的有m人,持有1元的有n人。
由于特殊情况,开始的时候,售票员没有零钱可找。
我们想知道这m+n名游客以什么样的顺序购票则可以顺利完成购票过程。
显然,m < n的时候,无论如何都不能完成;
m>=n的时候,有些情况也不行。比如,第一个购票的乘客就持有1元。
请计算出这m+n名游客所有可能顺利完成购票的不同情况的组合数目。
再假设持有5角的有m人,持有1元的有n人。
由于特殊情况,开始的时候,售票员没有零钱可找。
我们想知道这m+n名游客以什么样的顺序购票则可以顺利完成购票过程。
显然,m < n的时候,无论如何都不能完成;
m>=n的时候,有些情况也不行。比如,第一个购票的乘客就持有1元。
请计算出这m+n名游客所有可能顺利完成购票的不同情况的组合数目。
注意:只关心5角和1元交替出现的次序的不同排列,持有同样币值的两名游客交换位置并不算做一种新的情况来计数。
分析:
看到题的第一眼就知道使用递归解决问题了。
使用老套路, 分别模拟,每次购票,每次购票有可能是带1元的也有可能是带5角的。
分析,递归需要的参数,需要有一个售票员参数,售票员参数,是售票员手里面一开始有几张5角。
public class Main {
public static void main(String[] args) {
System.out.println(f(1,2,0));
System.out.println(f(2,2,0));
System.out.println(f(3,2,0));
System.out.println(f(5,3,0));
}
//m 持有5角的人
//n 持有1元的人
//c 售票员手里面有几张5角
public static int f(int m,int n,int c){
//当售票员手里的5角不够用时返回 0 代表该次模拟 不成功
if(c<0)return 0;
//当两种顾客都服务完了, 返回 1
if(m==0&&n==0)return 1;
//当5角钱的顾客服务完了,去服务1元钱的
if(m==0)return f(m,n-1,c-1);
//当1元钱的服务完了,去服务5角钱的
if(n==0)return f(m-1,n,c+1);
return f(m-1,n,c+1) + f(m,n-1,c-1);
}
}
使用小问题去验证递归正确性。
当 5 角的人数为 1 , 1元的人数 为 2
程序无论如何运行都会发生零钱不够找。
当 5 角的人数为 2 , 1元的人数 为 2
可能发生的排队情况 5 1 5 1、 5 5 1 1
当 5 角的人数为 3 , 1元的人数 为 2
可能发生的排队情况 5 5 5 1 1、5 5 1 5 1、5 5 1 1 5、5 1 5 5 1、5 1 5 1 5 。
改进递归。
public class Main {
public static void main(String[] args) {
System.out.println(f(1,2,0));
System.out.println(f(2,2,0));
System.out.println(f(3,2,0));
System.out.println(f(5,3,0));
}
//m 持有5角的人
//n 持有1元的人
//c 售票员手里面有几张5角
public static int f(int m,int n,int c){
//在所有变化过程中,一旦,m+c<n ,零钱怎么都不够找
if(m+c<n)return 0;
//当5角钱的顾客服务完了,只有一种方式,就是服务剩余的全部1元顾客
if(m==0)return 1;
//与上同理
if(n==0)return 1;
int r = f(m-1,n,c+1);
//只有当C大于零的时候才去尝试服务1元客户
if(c>0){
r+= f(m,n-1,c-1);
}
return r;
}
}
换个角度思考,再次改进递归。
之前是使用的常规角度去思考该问题,需要一个售票员参数,
之前考虑的是,从前往后对 5 角客户 1 元客户 进行服务,现在,考虑从后面开始
public class Main {
public static void main(String[] args) {
System.out.println(f(1,2));
System.out.println(f(2,2));
System.out.println(f(3,2));
System.out.println(f(5,3));
}
public static int f(int m,int n){
//每次往前推,都要考虑,此时此刻,5角 的客户, 还够不够找 1 元的
if(m<n)return 0;
//一元的没有了
if(n==0)return 1;
//5角的还剩下一个 -》》既然可以通过 m<n 的检验 说明此时 n应该 有 1 个或者0个。
//只有一种结果,先花 5 角的 再花 1 元的
if(m==1)return 1;
//假设最后一个服务的是5角客户,减少一个5角客户,计算m-1个5角用户 可能出现的排列种数,
//假设最后一个服务的是1元客户 减少一个1角客户,计算n-1个1元用户 可能出现的排列种数,
return f(m-1,n) + f(m,n-1);
}
}
递归精简完了,动态规划。
public class Main {
public static void main(String[] args) {
System.out.println(f(1,2));
System.out.println(f(2,2));
System.out.println(f(3,2));
System.out.println(f(5,3));
}
public static int f(int m,int n){
int[][] dp = new int[m+1][n+1];
for(int i=0;i<m+1;i++){
if(i>=0)dp[i][0] = 1;
}
for(int i=0;i<n+1;i++){
if(1>=i)dp[1][i] = 1;
}
for(int i=1;i<m+1;i++){
for(int j=1;j<n+1;j++){
if(i>=j){
dp[i][j] = dp[i-1][j] + dp[i][j-1];
}
}
}
return dp[m][n];
}
}