題目
一隻袋鼠要從河這邊跳到河對岸,河很寬,但是河中間打了很多樁子,每隔一米就有一個,每個樁子上都有一個彈簧,袋鼠跳到彈簧上就可以跳的更遠。每個彈簧力量不同,用一個數字代表它的力量,如果彈簧力量爲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;
}