搜狐2017秋招研發工程師筆試 —— 袋鼠過河(貪心、動態規劃、轉爲圖)

題目

測試鏈接

一隻袋鼠要從河這邊跳到河對岸,河很寬,但是河中間打了很多樁子,每隔一米就有一個,每個樁子上都有一個彈簧,袋鼠跳到彈簧上就可以跳的更遠。每個彈簧力量不同,用一個數字代表它的力量,如果彈簧力量爲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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章