搜狐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;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值