A
、B
两个人玩抢7游戏,游戏规则为A先报一个起始数字X
(10<X<10000
),B
报下一个数字Y
,(0<X-Y<3
),A
再报一个数字Z
(0<Y-Z<3
),以此类推,直到其中一个抢到7
,抢到7
即为胜者,在B
赢得比赛的情况下,一共有多少种组合?
起始数字M
,如100
10<=M<=10000
B
能赢得比赛的组合次数
10
1
只有一种赢的组合,A
起始选择10
,B
接着选择9
,A
接着选择8
,B
接着选择7
赢得胜利。
A
和B
每次选择的数字,只能比上一个数字小1
或2
,两个人所选择的数字越来越小直到降为7
。这种计算组合数的问题很容易想到使用动态规划来解题。
我们考虑动态规划三部曲:
dp
数组的含义是什么?由于A
和B
两个人是交替地进行数字选择的,我们可以构建两个dp
数组,dpA
和dpB
。
dpA[i]
表示A
选择了第i
个数字时的方法数,dpB[i]
表示B
选择了第i
个数字时的方法数。
由于两个人的选择是交替进行的,因此A
的状态由先前的B
转移得到,B
的状态由先前的A
转移得到。
当B
选择了数字i
时,上一轮A
的选择必然是i+1
或i+2
。因此B
选择数字i的组合数为A
选择了数字i+1
和i+2
的组合数的总和,即存在动态转移方程dpB[i] = dpA[i+1] + dpA[i+2]
。
对于A
选择了数字i
的情况,同理存在dpA[i] = dpB[i+1] + dpB[i+2]
由于选择是从大到小进行的,因此必须从M-1
开始进行逆序遍历,直到选择了7
。即
for i in range(M-1, 6, -1):
dpB[i] = dpA[i+1] + dpA[i+2]
dpA[i] = dpB[i+1] + dpB[i+2]
print(dpB[7])
最终dpB[7]
就是B
抢到了7
赢得最终胜利的方法数。
dp
数组如何初始化?由于dp过程是从M-1
开始的,当i = M-1
时,需要使用到i+2 = M+1
这个值,因此可以初始化dpA
和dpB
数组均为长度为M+2
的一维数组(或哈希表也可以,因为小于7
的那些位置实际上是没有用到的)。
由于A
最开始报的数字是M
,换句话说A
取得数字M
的方法数只有1
种,因此初始化dpA[M] = 1
,其余位置均初始化为0
。即
dpA = [0] * (M+2)
dpB = [0] * (M+2)
dpA[M] = 1
上述过程用到了两个dp
数组,看起来似乎比较冗长。
实际上可以把两个动态转移方程进行合并,只得到一个关于dpB
的动态转移方程。将
dpA[i+1] = dpB[i+2] + dpB[i+3]
和dpA[i+2] = dpB[i+3] + dpB[i+4]
代入dpB[i] = dpA[i+1] + dpA[i+2]
,可以得到新的动态转移方程dpB[i] = dpB[i+2] + 2*dpB[i+3] + dpB[i+4]
,这样就只需要一个dpB
数组和一个动态转移方程即可。
整体的代码修改为
M = int(input())
dpB = [0] * (M+2)
dpB[M-1] = 1
dpB[M-2] = 1
for i in range(M-3, 6, -1):
dpB[i] = dpB[i+2] + 2*dpB[i+3] + dpB[i+4]
print(dpB[7])
# 题目:【DP】2023C-抢7游戏
# 分值:100
# 作者:许老师-闭着眼睛学数理化
# 算法:DP
# 代码看不懂的地方,请直接在群上提问
# 输入A的初始报数
M = int(input())
# 初始化dpA和dpB两个初始数组
dpA = [0] * (M+2)
dpB = [0] * (M+2)
dpA[M] = 1
# dp过程,从M-1开始遍历,到7结束
for i in range(M-1, 6, -1):
dpB[i] = dpA[i+1] + dpA[i+2]
dpA[i] = dpB[i+1] + dpB[i+2]
# 输出dpB[7]即为最终答案
print(dpB[7])
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// 输入A的初始报数
long M = scanner.nextLong();
// 初始化dpA和dpB两个初始数组
long[] dpA = new long[(int) (M + 2)];
long[] dpB = new long[(int) (M + 2)];
dpA[(int) M] = 1;
// dp过程,从M-1开始遍历,到7结束
for (int i = (int) (M - 1); i >= 6; i--) {
dpB[i] = dpA[i + 1] + dpA[i + 2];
dpA[i] = dpB[i + 1] + dpB[i + 2];
}
// 输出dpB[7]即为最终答案
System.out.println(dpB[7]);
}
}
#include <iostream>
using namespace std;
int main() {
// 输入A的初始报数
long long M;
cin >> M;
// 初始化dpA和dpB两个初始数组
long long dpA[M + 2] = {0};
long long dpB[M + 2] = {0};
dpA[M] = 1;
// dp过程,从M-1开始遍历,到7结束
for (int i = M - 1; i >= 6; i--) {
dpB[i] = dpA[i + 1] + dpA[i + 2];
dpA[i] = dpB[i + 1] + dpB[i + 2];
}
// 输出dpB[7]即为最终答案
cout << dpB[7] << endl;
return 0;
}
时间复杂度:O(N)
。
空间复杂度:O(N)
。
华为OD算法/大厂面试高频题算法冲刺训练目前开始常态化报名!目前已服务100+同学成功上岸!
课程讲师为全网50w+粉丝编程博主@吴师兄学算法 以及小红书头部编程博主@闭着眼睛学数理化
每期人数维持在20人内,保证能够最大限度地满足到每一个同学的需求,达到和1v1同样的学习效果!
60+天陪伴式学习,40+直播课时,300+动画图解视频,300+LeetCode经典题,200+华为OD真题/大厂真题,还有简历修改、模拟面试、专属HR对接将为你解锁
可上全网独家的欧弟OJ系统练习华子OD、大厂真题
可查看链接 大厂真题汇总 & OD真题汇总(持续更新)
绿色聊天软件戳 od1336
了解更多