题目
一只袋鼠要从河这边跳到河对岸,河很宽,但是河中间打了很多桩子,每隔一米就有一个,每个桩子上都有一个弹簧,袋鼠跳到弹簧上就可以跳的更远。每个弹簧力量不同,用一个数字代表它的力量,如果弹簧力量为5,就代表袋鼠下一跳最多能够跳5米,如果为0,就会陷进去无法继续跳跃。河流一共N米宽,袋鼠初始位置就在第一个弹簧上面,要跳到最后一个弹簧之后就算过河了,给定每个弹簧的力量,求袋鼠最少需要多少跳能够到达对岸。如果无法到达输出-1 。
输入描述:
输入分两行,第一行是数组长度N (1 ≤ N ≤ 10000),第二行是每一项的值,用空格分隔。
输出描述:
输出最少的跳数,无法到达输出-1
示例1
输入
5
2 0 1 1 1输出
4
我觉得网站的测试用例不全面,你们可以用下面我单独给出的样例再测一下自己的贪心算法:
输入:
14
5 1 2 0 4 2 0 2 0 4 0 0 1 1
输出:
5
思路
方法一 转换为图求最短路径
把线性模型转换为有向图,判断是否存在源点到终点的最小路径即可,时间复杂度O(VE)。
N个木桩,编号 0 — N-1;
每一个木桩后面都可能跟着一个可达点集合,据此构建该点与其所有子节点的连接关系;
用 Bellman-ford 算法对该图以0号木桩为源点寻最小路径即可。
注意:因为到达木桩 N-1 时还不算过河,需要添加一个辅助木桩N作为终点
方法二 动态规划
明天再写…
方法三 贪心策略
只需要维护一个可达区间,复杂度O(n^2)。
① 从当前落点a,根据在该点可跳的最远步长得到可达集S
②从S中找下一跳能到达最远处的点Si
③令a = Si,重复①②步直到可达集S包含岸点,或无法继续更新可达集
例子如下:
14个点,每个点的可跳数如图
上图符号▲为每次跳到的点,箭头指向的是它可达区间的右边界
代码
方法一 转换为图求最短路径
#include <iostream>
#include <vector>
#include <algorithm>
#include <limits.h>
using namespace std;
int main() {
int N; cin >> N;
int *stone = new int[N + 1];
stone[N] = 0;// 辅助点,转换为图时需要该点作为终点,存在最小路径到该点则能过河
for (int i = 0; i < N; i++) cin >> stone[i];
// 构建邻接表, 时间复杂度O(V + E)
vector<vector<int> > map;
for (int curr_node = 0; curr_node < N + 1; curr_node++) {
vector<int> node;
for (int subNode = curr_node + 1; subNode <= curr_node + stone[curr_node] && subNode < N + 1; subNode++) {
node.push_back(subNode);
}
map.push_back(node);
}
// 用 Bellman - ford 找起始点到辅助终点的最小路径,时间复杂度O(VE)
vector<int> step(N + 1, INT_MAX);
step[0] = 0;
for (int i = 1; i < N + 1; i++) {
for (int node = 0; node < N + 1; node++) {
if (step[node] == INT_MAX) continue;
for (int sub_i = 0; sub_i < map[node].size(); sub_i++) {
int subNode = map[node][sub_i];
step[subNode] = min(step[subNode], step[node] + 1);
}
}
}
cout << (step[N] == INT_MAX ? -1 : step[N]) << endl;
return 0;
}
方法三 贪心策略
#include <iostream>
#include <vector>
#include <algorithm>
#include <limits.h>
using namespace std;
int main() {
// 初始化输入数据
int N = 0; cin >> N;
vector<int> stone(N);
for (int i = 0; i < N; i++) cin >> stone[i];
// 区间起点、区间右边界、新的区间起点、答案
int base, rightMost, new_base, res;
// 到达每个点的跳数
vector<int> step(N); step[0] = 0;
bool is_ok = false;
for ( base = 0, rightMost = stone[0]; base < N; base = new_base) {
// 更新可达区间
rightMost = new_base = base;
for (int i = base + 1; i <= base + stone[base] && i < N; i++) {
// 避免对之前已经能到达的点重复加步数
if(!step[i]) step[i] = step[base] + 1;
if (stone[i] && rightMost < i + stone[i]) {
new_base = i;
rightMost = i + stone[i];
//已经能到河对岸,终止循环
if (rightMost >= N) {
res = (i == N - 1 ?step[N - 1] + 1: step[new_base] + 1);
is_ok = true;
break;
}
}
}
// 过河
if (is_ok) break;
// 可达区间为 0 ,没法再继续跳
if (base == new_base) break;
}
cout << (is_ok? res: -1)<< endl;
return 0;
}