1. 项目背景与核心挑战
这道来自蓝桥杯算法提高VIP题库的"超级玛丽"题目,本质上是一个经典的动态规划问题变种。题目描述通常为:给定一个长度为n的平台序列,其中某些平台存在陷阱(用1表示),玛丽需要从起点跳到终点,每次可以跳跃1或2个平台,求安全到达终点的路径总数。
在实际解题过程中,我发现这个题目完美复刻了经典爬楼梯问题的解题框架,但增加了陷阱判断的约束条件。对于算法竞赛选手而言,这类题目考察的核心能力在于:如何将实际问题抽象为状态转移方程,并处理边界条件。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 问题建模与状态定义
2.1 基础状态转移方程
我们先考虑没有陷阱的情况,这与斐波那契数列问题完全一致:
- dp[i] = dp[i-1] + dp[i-2]
- 初始条件:dp[0]=1(起点),dp[1]=1(如果第一个平台安全)
2.2 陷阱条件处理
当引入陷阱后,状态转移需要增加判断:
python复制if 平台i有陷阱:
dp[i] = 0
else:
dp[i] = dp[i-1] + dp[i-2] (如果i-1和i-2存在)
2.3 边界条件处理
实际编码时需要特别注意:
- 起始平台是否安全(题目通常保证安全)
- 连续两个陷阱导致无法到达的情况
- 平台数量为0或1的特殊情况
3. 算法实现与优化
3.1 基础实现方案
最直观的解法是使用一维数组存储中间结果:
python复制def super_mary(n, traps):
dp = [0] * n
dp[0] = 0 if traps[0] else 1
if n > 1:
dp[1] = 0 if traps[1] else (1 + (0 if traps[0] else 1))
for i in range(2, n):
if traps[i]:
dp[i] = 0
else:
dp[i] = dp[i-1] + dp[i-2]
return dp[-1]
3.2 空间优化方案
观察到每个状态只依赖前两个状态,可以优化空间复杂度到O(1):
python复制def super_mary_optimized(n, traps):
if n == 0: return 0
if traps[0]: return 0
a, b = 1, 1
for i in range(1, n):
if traps[i]:
current = 0
else:
current = a + (b if i > 1 else 1)
a, b = current, a
return a
4. 常见错误与调试技巧
4.1 典型错误案例
- 初始条件错误:
python复制# 错误写法:忽略起点可能有陷阱
dp[0] = 1 # 应该先判断traps[0]
- 索引越界:
python复制# 当n=1时直接访问dp[1]会导致越界
if n > 1: # 必须添加此判断
dp[1] = ...
- 连续陷阱未处理:
python复制# 当i-1和i-2都是陷阱时,应该提前终止
if dp[i-1] == 0 and dp[i-2] == 0:
return 0 # 无法到达终点
4.2 调试建议
- 打印dp表:
python复制print(f"i={i}, dp={dp}") # 观察状态变化
- 测试用例设计:
- 最小用例:n=1
- 全陷阱用例:[1,1,1]
- 交替陷阱:[0,1,0,1]
- 结尾陷阱:[0,0,0,1]
- 性能测试:
- 大n值测试(如n=1e5)
- 全0大数组压力测试
5. 算法扩展与变种
5.1 允许跳跃3步
如果题目改为允许跳1、2或3步:
python复制dp[i] = dp[i-1] + dp[i-2] + dp[i-3]
5.2 不同跳跃代价
如果每次跳跃消耗不同体力:
python复制dp[i] = min(dp[i-1]+cost1, dp[i-2]+cost2)
5.3 多维平台跳跃
扩展到二维矩阵平台:
python复制# 需要二维dp表,考虑上下左右移动
dp[i][j] = dp[i-1][j] + dp[i][j-1] (如果没有障碍)
6. 竞赛技巧与时间优化
- 输入处理优化:
python复制# 使用快速输入方法
import sys
n = int(sys.stdin.readline())
traps = list(map(int, sys.stdin.readline().split()))
- 提前终止判断:
python复制if dp[i] == 0 and dp[i-1] == 0:
return 0 # 后续无法到达
- 模运算处理:
python复制# 题目要求取模时
dp[i] = (dp[i-1] + dp[i-2]) % MOD
- 记忆化搜索实现:
python复制from functools import lru_cache
@lru_cache(maxsize=None)
def dfs(pos):
if pos >= n: return 0
if traps[pos]: return 0
if pos == n-1: return 1
return dfs(pos+1) + dfs(pos+2)
7. 实际编码建议
7.1 Python实现模板
python复制def solve():
import sys
input = sys.stdin.read
data = input().split()
n = int(data[0])
traps = list(map(int, data[1:n+1]))
if n == 0: return 0
if traps[0]: return 0
dp = [0] * n
dp[0] = 1
for i in range(1, n):
if traps[i]:
dp[i] = 0
continue
ways = dp[i-1]
if i >= 2:
ways += dp[i-2]
elif i == 1:
ways += 1 # 从起点直接跳两步到i=1
dp[i] = ways
return dp[-1]
print(solve())
7.2 C++优化版本
cpp复制#include <iostream>
#include <vector>
using namespace std;
int main() {
int n;
cin >> n;
vector<int> traps(n);
for (int i = 0; i < n; ++i) {
cin >> traps[i];
}
if (n == 0) { cout << 0; return 0; }
if (traps[0]) { cout << 0; return 0; }
int prev2 = 1; // dp[i-2]
int prev1 = 1; // dp[i-1]
for (int i = 1; i < n; ++i) {
int current = 0;
if (!traps[i]) {
current += prev1;
if (i >= 2) {
current += prev2;
} else {
current += 1; // 初始状态处理
}
}
prev2 = prev1;
prev1 = current;
}
cout << prev1;
return 0;
}
8. 复杂度分析与证明
8.1 时间复杂度
基础实现:
- 单次遍历O(n)
- 每个位置常数时间操作
- 总体O(n)
8.2 空间复杂度
- 基础dp数组:O(n)
- 优化版本:O(1)
8.3 正确性证明
数学归纳法:
- 基准情况:n=0和n=1时显然正确
- 归纳假设:假设对于所有k<i,dp[k]正确
- 归纳步骤:
- 根据转移方程,dp[i]只依赖dp[i-1]和dp[i-2]
- 如果i位置有陷阱,确实无法到达(dp[i]=0)
- 否则路径数为前两个位置之和
9. 同类题目推荐
- LeetCode 70. 爬楼梯(基础版)
- LeetCode 746. 使用最小花费爬楼梯
- 洛谷P1255 数楼梯(高精度版)
- Codeforces 189A. Cut Ribbon(物品切割问题)
- AtCoder DP Contest A - Frog 1(基础DP)
10. 个人解题心得
在实际竞赛中遇到这类题目时,我通常会采取以下步骤:
- 先手算小样例验证理解是否正确
- 写出基础状态转移方程
- 处理特殊边界条件(特别是起点/终点陷阱)
- 考虑空间优化可能性
- 设计极端测试用例验证鲁棒性
一个容易忽略的细节是:当平台数n=1时,如果该平台安全,路径数应该是1(不跳即为到达),很多选手会错误返回0。另一个陷阱是题目可能给出n=0的情况,需要特别处理。
