文章目录
最短跳跃步数问题(C++实现)
这个问题要求我们找到从起点 a
到终点 b
的最少跳跃次数,每次可以向左或向右跳跃 to[now]
的距离。下面我将提供几种不同的解决方案,并分析它们的优缺点。
1. BFS 解法(推荐)
广度优先搜索(BFS)天然适合求解最短路径问题,因为它会逐层扩展搜索。
#include <iostream>
#include <queue>
#include <cstring>
using namespace std;
const int N = 2e4 + 10;
int to[N], vis[N];
int n, a, b;
int bfs() {
if (a == b) return 0;
queue<pair<int, int>> q; // {当前位置, 当前步数}
q.push({a, 0});
vis[a] = 1;
while (!q.empty()) {
auto [now, step] = q.front();
q.pop();
// 尝试向右跳
int right = now + to[now];
if (right <= n && !vis[right]) {
if (right == b) return step + 1;
vis[right] = 1;
q.push({right, step + 1});
}
// 尝试向左跳
int left = now - to[now];
if (left >= 1 && !vis[left]) {
if (left == b) return step + 1;
vis[left] = 1;
q.push({left, step + 1});
}
}
return -1;
}
int main() {
cin >> n >> a >> b;
for (int i = 1; i <= n; ++i) cin >> to[i];
memset(vis, 0, sizeof(vis));
int res = bfs();
cout << res << endl;
return 0;
}
时间复杂度:O(n),每个节点最多被访问一次
空间复杂度:O(n),用于队列和访问标记
2. DFS 解法(带剪枝)
深度优先搜索也可以解决,但需要剪枝来优化。
#include <iostream>
#include <climits>
#include <cstring>
using namespace std;
const int N = 2e4 + 10;
int to[N], vis[N];
int n, a, b, ans = INT_MAX;
void dfs(int now, int step) {
if (now == b) {
ans = min(ans, step);
return;
}
if (step >= ans) return; // 剪枝
vis[now] = 1;
// 向右跳
int right = now + to[now];
if (right <= n && !vis[right]) {
dfs(right, step + 1);
}
// 向左跳
int left = now - to[now];
if (left >= 1 && !vis[left]) {
dfs(left, step + 1);
}
vis[now] = 0; // 回溯
}
int main() {
cin >> n >> a >> b;
for (int i = 1; i <= n; ++i) cin >> to[i];
memset(vis, 0, sizeof(vis));
dfs(a, 0);
cout << (ans == INT_MAX ? -1 : ans) << endl;
return 0;
}
时间复杂度:最坏情况O(2^n),但剪枝后实际运行会快很多
空间复杂度:O(n),递归栈深度
3. 动态规划解法
这个问题也可以使用动态规划来解决,但实现起来不如BFS直观。
#include <iostream>
#include <vector>
#include <climits>
using namespace std;
const int N = 2e4 + 10;
int main() {
int n, a, b;
cin >> n >> a >> b;
vector<int> to(n + 1);
for (int i = 1; i <= n; ++i) cin >> to[i];
vector<int> dp(n + 1, INT_MAX);
dp[a] = 0;
bool updated = true;
for (int k = 0; k < n && updated; ++k) {
updated = false;
vector<int> temp = dp;
for (int i = 1; i <= n; ++i) {
if (dp[i] != INT_MAX) {
// 向右跳
int right = i + to[i];
if (right <= n && dp[i] + 1 < temp[right]) {
temp[right] = dp[i] + 1;
updated = true;
}
// 向左跳
int left = i - to[i];
if (left >= 1 && dp[i] + 1 < temp[left]) {
temp[left] = dp[i] + 1;
updated = true;
}
}
}
dp = temp;
}
cout << (dp[b] == INT_MAX ? -1 : dp[b]) << endl;
return 0;
}
时间复杂度:O(n^2)
空间复杂度:O(n)
算法比较
方法 | 时间复杂度 | 空间复杂度 | 适用场景 |
---|---|---|---|
BFS | O(n) | O(n) | 最优解,推荐使用 |
DFS | 最坏O(2^n) | O(n) | 小规模数据,需要剪枝 |
DP | O(n^2) | O(n) | 理论可行,但不如BFS高效 |
输入输出示例
输入1:
5 1 5
3 3 1 2 5
输出1:
3
解释: 1 → 4 → 2 → 5
输入2:
5 1 2
1 2 1 2 1
输出2:
1
解释: 可以直接从1跳到2
输入3:
3 1 3
1 1 1
输出3:
-1
解释: 无法到达终点3
总结
对于这个问题(最短跳跃步数问题),BFS是最优的解决方案,因为它能保证在O(n)时间内找到最短路径。DFS虽然可行,但在最坏情况下性能较差。动态规划的实现相对复杂,且时间复杂度较高。在实际编程竞赛中,推荐使用BFS来解决这类最短路径问题。
栗子: 奇怪的电梯问题🦉🦉
问题描述
假设有一个奇怪的电梯,共有N层(1~N)。给定:
当前所在楼层A
目标楼层B
每层楼有一个数字K_i,表示可以从该层上移K_i层或下移K_i层(不能超出楼层范围)
要求计算从A到B的最少按键次数(即最少移动次数),如果无法到达则返回-1。
BFS解法(推荐)
#include <iostream>
#include <queue>
#include <cstring>
using namespace std;
const int MAXN = 205; // 假设最大楼层数
int K[MAXN]; // 每层的移动数字
int vis[MAXN]; // 访问标记数组
int N, A, B; // 总楼层数, 起始层, 目标层
int bfs() {
if (A == B) return 0; // 已经在目标层
queue<pair<int, int>> q; // {当前楼层, 按键次数}
q.push({A, 0});
vis[A] = 1;
while (!q.empty()) {
auto [current, steps] = q.front();
q.pop();
// 向上移动
int up = current + K[current];
if (up <= N && !vis[up]) {
if (up == B) return steps + 1;
vis[up] = 1;
q.push({up, steps + 1});
}
// 向下移动
int down = current - K[current];
if (down >= 1 && !vis[down]) {
if (down == B) return steps + 1;
vis[down] = 1;
q.push({down, steps + 1});
}
}
return -1; // 无法到达
}
int main() {
cin >> N >> A >> B;
for (int i = 1; i <= N; ++i) {
cin >> K[i];
}
memset(vis, 0, sizeof(vis));
int result = bfs();
cout << result << endl;
return 0;
}
代码解析
-
初始化:
- 定义最大楼层数MAXN
- K数组存储每层的移动数字
- vis数组记录已访问的楼层
-
BFS函数:
- 使用队列存储待处理的楼层和当前步数
- 每次处理队列头部元素,尝试向上/向下移动
- 如果到达目标楼层,立即返回当前步数
- 如果队列处理完毕仍未找到目标,返回-1
-
主函数:
- 读取输入数据
- 初始化访问数组
- 调用BFS并输出结果
DFS解法(带剪枝)
#include <iostream>
#include <climits>
using namespace std;
const int MAXN = 205;
int K[MAXN], vis[MAXN];
int N, A, B, min_steps = INT_MAX;
void dfs(int current, int steps) {
if (current == B) {
min_steps = min(min_steps, steps);
return;
}
if (steps >= min_steps) return; // 剪枝
vis[current] = 1;
// 向上移动
int up = current + K[current];
if (up <= N && !vis[up]) {
dfs(up, steps + 1);
}
// 向下移动
int down = current - K[current];
if (down >= 1 && !vis[down]) {
dfs(down, steps + 1);
}
vis[current] = 0; // 回溯
}
int main() {
cin >> N >> A >> B;
for (int i = 1; i <= N; ++i) {
cin >> K[i];
}
dfs(A, 0);
cout << (min_steps == INT_MAX ? -1 : min_steps) << endl;
return 0;
}
算法比较
方法 | 时间复杂度 | 空间复杂度 | 适用场景 |
---|---|---|---|
BFS | O(N) | O(N) | 最优解,推荐使用 |
DFS | 最坏O(2^N) | O(N) | 小规模数据,需要剪枝 |
输入输出示例
输入1:
5 1 5
3 3 1 2 5
输出1:
3
解释: 1 → 4 → 2 → 5
输入2:
5 1 2
1 2 1 2 1
输出2:
1
解释: 可以直接从1跳到2
输入3:
3 1 3
1 1 1
输出3:
-1
解释: 无法到达目标楼层3
优化建议
-
双向BFS:可以同时从起点和终点开始搜索,当两个搜索相遇时停止,可以进一步提高效率。
-
预处理:如果问题规模特别大,可以考虑预处理楼层间的可达关系。
-
记忆化搜索:DFS可以结合记忆化技术,避免重复计算。
总结
对于"奇怪的电梯"这类最短路径问题,BFS是最佳选择,因为它能保证找到最短路径且时间复杂度较低。DFS虽然实现简单,但在最坏情况下性能较差。在实际应用中,建议优先使用BFS解法。🐍🐍🐍