文章目录

  • 1. 题目来源
  • 2. 题目说明
  • 3. 题目解析
    • 方法一:哈希表、记忆化搜索、递归解法
    • 方法二:迭代解法
    • 方法三:回溯法+贪心策略+剪枝

1. 题目来源

链接:青蛙过河
来源:LeetCode

2. 题目说明

一只青蛙想要过河。 假定河流被等分为 x 个单元格,并且在每一个单元格内都有可能放有一石子(也有可能没有)。 青蛙可以跳上石头,但是不可以跳入水中。

给定石子的位置列表(用单元格序号升序表示), 请判定青蛙能否成功过河(即能否在最后一步跳至最后一个石子上)。 开始时, 青蛙默认已站在第一个石子上,并可以假定它第一步只能跳跃一个单位(即只能从单元格1跳至单元格2)。

如果青蛙上一步跳跃了 k 个单位,那么它接下来的跳跃距离只能选择为 k - 1、k 或 k + 1个单位。 另请注意,青蛙只能向前方(终点的方向)跳跃。

请注意:

石子的数量 ≥ 2 且 < 1100;
每一个石子的位置序号都是一个非负整数,且其 < 2312^{31}231;
第一个石子的位置永远是0。

示例1:

[0,1,3,5,6,8,12,17]
总共有8个石子。
第一个石子处于序号为0的单元格的位置, 第二个石子处于序号为1的单元格的位置,
第三个石子在序号为3的单元格的位置, 以此定义整个数组…
最后一个石子处于序号为17的单元格的位置。
返回 true。即青蛙可以成功过河,按照如下方案跳跃:
跳1个单位到第2块石子, 然后跳2个单位到第3块石子, 接着
跳2个单位到第4块石子, 然后跳3个单位到第6块石子,
跳4个单位到第7块石子, 最后,跳5个单位到第8个石子(即最后一块石子)。

示例2:

[0,1,2,3,4,8,9,11]
返回 false。青蛙没有办法过河。
这是因为第5和第6个石子之间的间距太大,没有可选的方案供青蛙跳跃过去。

3. 题目解析

方法一:哈希表、记忆化搜索、递归解法

首先要理解青蛙跳到某个石头上可能有多种跳法,由于这道题只是让判断青蛙是否能跳到最后一个石头上,并没有让我们返回所有的路径,这样就降低了一些难度。下面为递归做法思路:

  • 使用记忆化搜索,维护一个哈希表,建立青蛙在 pos 位置和拥有 jump 跳跃能力时是否能跳到对岸
  • 为了能用一个变量同时表示 pos 和 jump,可以将 jump 左移很多位并或上 pos,由于题目中对于位置大小有限制,所以不会产生冲突
  • 首先判断 pos 是否已经到最后一个石头了,是的话直接返回 true
  • 然后查看当前这种情况是否已经出现在哈希表中,是的话直接从哈希表中取结果
  • 如果没有,就遍历余下的所有石头,对于遍历到的石头,计算到当前石头的距离 dist
  • 如果 dist 小于 jump - 1,接着遍历下一块石头
  • 如果 dist 大于 jump + 1,说明无法跳到下一块石头,m[key] 赋值为 false,并返回 false
  • 如果在青蛙能跳到的范围中,调用递归函数,以新位置 i 为 pos,距离 dist 为 jump,如果返回 true 了,即给 m[key] 赋值为 true,并返回 true
  • 如果结束遍历给 m[key] 赋值为 false,并返回 false

参见代码如下:

// 执行用时 :40 ms, 在所有 C++ 提交中击败了94.12%的用户
// 内存消耗 :13.9 MB, 在所有 C++ 提交中击败了81.25%的用户class Solution {public:bool canCross(vector<int>& stones) {unordered_map<int, bool> m;return help(stones, 0, 0, m);}bool help(vector<int>& stones, int pos, int jump, unordered_map<int, bool>& m) {int n = stones.size(), key = pos | jump << 11;if (pos >= n - 1) return true;if (m.count(key)) return m[key];for (int i = pos + 1; i < n; ++i) {int dist = stones[i] - stones[pos];if (dist < jump - 1) continue;if (dist > jump + 1) return m[key] = false;if (help(stones, i, dist, m)) return m[key] = true;}return m[key] = false;}
};
方法二:迭代解法

也可以用迭代的方法来解,思路如下:

  • 用一个哈希表来建立每个石头和在该位置上能跳的距离之间的映射
  • 建立一个一维 dp 数组,其中 dp[i] 表示在位置为 i 的石头青蛙的弹跳力(只有青蛙能跳到该石头上,dp[i]才大于0)
  • 由于题目中规定了第一个石头上青蛙跳的距离必须是 1,为了跟后面的统一,对青蛙在第一块石头上的弹跳力初始化为 0 (虽然为 0,但是由于题目上说青蛙最远能到其弹跳力 +1 的距离,所以仍然可以到达第二块石头)。
  • 用变量 k 表示当前石头,然后开始遍历剩余的石头
  • 对于遍历到的石头 i,来找到刚好能跳到 i 上的石头 k,如果 i 和 k 的距离大于青蛙在 k 上的弹跳力 +1,则说明青蛙在 k 上到不了 i,则 k 自增 1
  • 从 k 遍历到 i,如果青蛙能从中间某个石头上跳到 i 上,则更新石头 i 上的弹跳力和最大弹跳力
  • 这样当循环完成后,只要检查最后一个石头上青蛙的最大弹跳力是否大于0即可

参见代码如下:

// 执行用时 :140 ms, 在所有 C++ 提交中击败了66.29%的用户
// 内存消耗 :35.5 MB, 在所有 C++ 提交中击败了36.81%的用户class Solution {public:bool canCross(vector<int>& stones) {unordered_map<int, unordered_set<int>> m;vector<int> dp(stones.size(), 0);m[0].insert(0);int k = 0;for (int i = 1; i < stones.size(); ++i) {while (dp[k] + 1 < stones[i] - stones[k]) ++k;for (int j = k; j < i; ++j) {int t = stones[i] - stones[j];if (m[j].count(t - 1) || m[j].count(t) || m[j].count(t + 1)) {m[i].insert(t);dp[i] = max(dp[i], t);}}}return dp.back() > 0;}
};
方法三:回溯法+贪心策略+剪枝

和上述几种方法大致思路一样:由于当前可跳的步长取决于上一次调到次石头上的步长,所以将上一次可达此石头的步长保存,然后根据上一次的到达此石头的步长集合选择当前可跳的步长。

// 执行用时 :616 ms, 在所有 C++ 提交中击败了15.61%的用户
// 内存消耗 :38.9 MB, 在所有 C++ 提交中击败了14.94%的用户
class Solution {public:bool canCross(vector<int>& stones) {//第一步只能跳一个不长if (stones[1] != stones[0] + 1) {return false;}int endStone = *(--stones.end());//尾端石头set<int> stonesSet(++stones.begin(), stones.end());//将vector容器转换为set容器map<int, set<int> > myMap;//myMap[i]标记i可跳的不长myMap[*stonesSet.begin()].insert(1);//初始只能跳一步到第二个位置//顺序访问所有石头for (auto stone : stonesSet) {//根据上一次到达此地的步长集合,计算下一步可跳的步长for (auto nextStone : myMap[stone]) {//步长nextStone - 1if (nextStone > 1 && stonesSet.find(stone + nextStone - 1) != stonesSet.end()) {//如果跳nextStone - 1后到达的石头在stonesSet中,说明stone + nextStone - 1这块石头可由不长nextStone - 1到达myMap[stone + nextStone - 1].insert(nextStone - 1);}//步长nextStoneif (stonesSet.find(stone + nextStone) != stonesSet.end()) {//如果跳nextStone 后到达的石头在stonesSet中,说明stone + nextStone这块石头可由不长nextStone到达myMap[stone + nextStone].insert(nextStone);}//步长nextStone + 1if (stonesSet.find(stone + nextStone + 1) != stonesSet.end()) {//如果跳nextStone + 1后到达的石头在stonesSet中,说明stone + nextStone + 1这块石头可由不长nextStone + 1到达myMap[stone + nextStone + 1].insert(nextStone + 1);}//如果已经达到了endStoneif (myMap.count(endStone) > 0) {return true;}}}return false;}
};

剪枝:

// 执行用时 :24 ms, 在所有 C++ 提交中击败了96.83%的用户
// 内存消耗 :12.3 MB, 在所有 C++ 提交中击败了86.81%的用户
class Solution {private:set<int> stonesSet;
public://从nowStone开始搜索能否到达last,beforeStep为到达nowStone的步长bool jump(int nowStone, int beforeStep, int last) {//如果已经达到了终点if ((nowStone + beforeStep + 1) == last || (nowStone + beforeStep) == last || (nowStone + beforeStep - 1) == last){return true;}//采取贪心策略,每次都先选择beforeStep + 1步长if (stonesSet.find(nowStone + beforeStep + 1) != stonesSet.end() && jump(nowStone + beforeStep + 1, beforeStep + 1, last)) {return true;}//再beforeStep步长if (stonesSet.find(nowStone + beforeStep) != stonesSet.end()&& jump(nowStone + beforeStep, beforeStep, last)) {return true;}//最后beforeStep - 1步长if (beforeStep > 1 && stonesSet.find(nowStone + beforeStep - 1) != stonesSet.end()&& jump(nowStone + beforeStep - 1, beforeStep - 1, last)) {return true;}return false;}bool canCross(vector<int>& stones) {if (stones[1] != 1) return false;int last = stones.back();for (int i = 1; i < stones.size(); ++i) {if (i > 3 && stones[i - 1] * 2 < stones[i]){//剪枝算法,stones[i - 1]最多是有步长stones[i - 1]到达//stones[i - 1] * 2 < stones[i],说明无论如何stones[i]都不可达return false;}stonesSet.insert(stones[i]);}return jump(1, 1, last);}
};

[每日一题] 128. 青蛙过河(数组、记忆化搜索、递归、剪枝)相关推荐

  1. 山东省第五届省赛题C Colorful Cupcakes(五维数组+记忆化搜索)

    在写题目之前先来介绍一下记忆化搜索. 算法上依然是搜索的流程,但是搜索到的一些解用动态规划那种思想和模式保存.一般来说,动态规划总要遍历所有的状态,而搜索可以排除一些无效的状态.最最最最最主要的是,搜 ...

  2. 记忆化搜索 递归缓存_需要微缓存吗? 营救记忆

    记忆化搜索 递归缓存 缓存解决了各种各样的性能问题. 有很多方法可以将缓存集成到我们的应用程序中. 例如,当我们使用Spring时,可以轻松使用@Cacheable支持. 非常简单,但我们仍然必须配置 ...

  3. [Leetcode][第322题][JAVA][零钱兑换][回溯][记忆化搜索][动态规划]

    [问题描述][中等] [解答思路] 1. 递归(超时) class Solution {int res = Integer.MAX_VALUE;public int coinChange(int[] ...

  4. [Leetcode][第312题][JAVA][戳气球][动态规划][记忆化搜索]

    [问题描述][困难] [解答思路] 1. 记忆化搜索 时间复杂度:O(n^3) 空间复杂度:O(n^2) class Solution {public int[][] rec;public int[] ...

  5. [蓝桥杯][2014年第五届真题]地宫取宝(记忆化搜索)

    题目描述 X 国王有一个地宫宝库.是 n x m 个格子的矩阵.每个格子放一件宝贝.每个宝贝贴着价值标签. 地宫的入口在左上角,出口在右下角. 小明被带到地宫的入口,国王要求他只能向右或向下行走. 走 ...

  6. 2015 UESTC 搜索专题B题 邱老师降临小行星 记忆化搜索

    邱老师降临小行星 Time Limit: 20 Sec  Memory Limit: 256 MB 题目连接 http://acm.uestc.edu.cn/#/contest/show/61 Des ...

  7. 数据结构-记忆化搜索讲解

    算法:记忆化搜索算法 一:简述 记忆化搜索实际上是递归来实现的,但是递归的过程中有许多的结果是被反复计算的,这样会大大降低算法的执行效率. 而记忆化搜索是在递归的过程中,将已经计算出来的结果保存起来, ...

  8. 每日一题:给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。

    每日一题:给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序. 2020年11月19日,力扣,简单,移动零 一.题目描述 给定一个数组 nums,编写一个函数 ...

  9. 【每日一题Day95】LC1815得到新鲜甜甜圈的最多组数 | 状态压缩dp 记忆化搜索

    得到新鲜甜甜圈的最多组数[LC1815] 有一个甜甜圈商店,每批次都烤 batchSize 个甜甜圈.这个店铺有个规则,就是在烤一批新的甜甜圈时,之前 所有 甜甜圈都必须已经全部销售完毕.给你一个整数 ...

最新文章

  1. DELL服务器重做RAID
  2. NIPS改名被否,而在改名分歧之外我们能做的还有很多
  3. Java基础:常见对象
  4. python format 槽中槽_printf中的槽和实参--对比python struct包
  5. onclick 传参,用转义符进行转义。
  6. cf1557D. Ezzat and Grid
  7. 索引和未索引执行计划的比较_详解Oracle复合索引+实例说明
  8. 天池 在线编程 牛郎织女(广度优先搜索)
  9. 【每日SQL打卡】​​​​​​​​​​​​​​​DAY 11丨产品销售分析 II【难度简单】
  10. VUE中出现 Cannot read property ‘length‘ of undefined 的错误
  11. 如何选择正确的控制系统?PLC和DCS各有不同
  12. 零点定理和罗尔定理的完善?
  13. C#版OPCClient代码总结
  14. 【python 淘宝爬虫】淘宝信誉分抓取
  15. 如何用Visual Studio 2022 编写C语言
  16. 218本巴菲特、芒格及段永平推荐书籍下载 (2012-03-31 22:53:28)
  17. 深度优先搜索dfs算法刷题笔记【蓝桥杯】
  18. 逃脱“黑天鹅”魔咒:荣耀逆势增长背后的“反脆弱”之力
  19. 学完可入狱:《Web渗透测试-基于Kali Linux》经典黑客入门教程
  20. 《浪潮之巅》13 幕后的英雄--风险投资

热门文章

  1. 转载:技术大停滞——范式春梦中的地球工业文明6 台阶的本质:复杂度魔鬼
  2. 大疆文档(3)-开发流程
  3. 【心酸】从上一家公司到下一家公司
  4. PR 审批界面增加显示项 解决方法
  5. pycharm的py文件抬头文件头模板
  6. 【新手向】PCB从淘宝到交差
  7. ActiveMQ 消息中间件
  8. 海外微信公众号被关注后不能自动回复消息的解决方案
  9. U盘安装win7+linux(centos)双系统详细教程
  10. Oracle查询数据,其中指定字段重复的只取其中一条