跳跃游戏 (DFS->记忆化搜索->动态规划/贪心证明)

news2024/12/23 23:36:46

一.跳跃游戏简单介绍

1. 跳跃游戏简单介绍

        跳跃游戏是一种典型的算法题目,经常是给定一数组arr,从数组的某一位置i出发,根据一定的跳跃规则,比如从i位置能跳arr[i]步,或者小于arr[i]步,或者固定步数,直到到达某一位置,可能是数组的最后一个位置,也有可能是某一特别的数值处,也有可能在这个过程中,可能需要求解可能存在的最大值或者最小值。

        对于跳跃游戏类的题目,经常使用贪心、动态规划、dfs、bfs等方法解决,对于可以使用dfs解决的题目,经常也可以使用动态规划,但一般贪心可以有更好的时间复杂度和空间复杂度。还有经常使用的动态规划剪枝、前缀和、滑动窗口和BFS,由于在大部分情况下,能用DFS解决的题目都可以用BFS解决,且两种方法有基本相同的复杂度,尤其是在跳跃游戏这类题目中,可以视为一种方法。

        本文借青蛙酱主要复习动态规划三部曲。

2.跳跃游戏典型题目

(1)leetcode70 爬楼梯

(2)leetcode剑指 Offer 10- II. 青蛙跳台阶问题

(3)剑指 Offer II 088. 爬楼梯的最少成本

(4) leetcode55 跳跃游戏

(5)leetcode45 跳跃游戏 II

(6) leetcode1306 跳跃游戏 III

(7)leetcode1696 跳跃游戏 VI

(8)leetcode1871 跳跃游戏 VII

(9)leetcode1413 逐步求和得到正数的最小值

以上部分,请见:

跳跃游戏 (贪心/动态规划/dfs)

跳跃游戏 (动态规划剪枝/前缀和/滑动窗口/BFS剪枝)

3.动态规划三步曲复习

Java-算法-动态规划

二.跳跃游戏相关专题-青蛙酱🐸的奇妙冒险

1. 剑指 Offer 10- II 青蛙跳台阶问题

见 一.2.(2)

2. leetcode 2498 青蛙过河 II

给你一个下标从 0 开始的整数数组 stones ,数组中的元素 严格递增 ,表示一条河中石头的位置。

一只青蛙一开始在第一块石头上,它想到达最后一块石头,然后回到第一块石头。同时每块石头 至多 到达 一次。

一次跳跃的 长度 是青蛙跳跃前和跳跃后所在两块石头之间的距离。

更正式的,如果青蛙从 stones[i] 跳到 stones[j] ,跳跃的长度为 |stones[i] - stones[j]| 。
一条路径的 代价 是这条路径里的 最大跳跃长度 。

请你返回这只青蛙的 最小代价 。

输入:stones = [0,2,5,6,7]
输出:5
解释:上图展示了一条最优路径。
这条路径的代价是 5 ,是这条路径中的最大跳跃长度。
无法得到一条代价小于 5 的路径,我们返回 5 。

输入:stones = [0,3,9]
输出:9
解释:
青蛙可以直接跳到最后一块石头,然后跳回第一块石头。
在这条路径中,每次跳跃长度都是 9 。所以路径代价是 max(9, 9) = 9 。
这是可行路径中的最小代价。

class Solution {
    public int maxJump(int[] stones) {
        if(stones.length <= 2) return stones[1]-stones[0];
        int ans = stones[2]-stones[0];
        for(int i = 2; i < stones.length; i++) ans = Math.max(ans,stones[i]-stones[i-2]);
        return ans;
    }
}

本题小结:

(1)首先思考,是跳所有的石头产生的结果最小还是漏过一些石头产生的结果最小,很显然,把所有的石头都跳过,在中间相当于插值,所产生的结果才有可能是最小的,即min(跳所有的石头)<=min(漏过一些石头)

(2)一句话贪心证明:大于三个的一段距离都可以进行分解成更小的段

 

3. leetcode 403. 青蛙过河

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

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

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

输入:stones = [0,1,3,5,6,8,12,17]
输出:true
解释:青蛙可以成功过河,按照如下方案跳跃:跳 1 个单位到第 2 块石子, 
然后跳 2 个单位到第 3 块石子, 接着 跳 2 个单位到第 4 块石子, 
然后跳 3 个单位到第 6 块石子, 跳 4 个单位到第 7 块石子, 最后,
跳 5 个单位到第 8 个石子(即最后一块石子)。

输入:stones = [0,1,2,3,4,8,9,11]
输出:false
解释:这是因为第 5 和第 6 个石子之间的间距太大,没有可选的方案供青蛙跳跃过去。

DFS

class Solution {
    int[] stones;
    HashMap<Integer,Integer> map = new HashMap<>();
    boolean flag = false;
    public boolean canCross(int[] stones) {
        this.stones = stones;
        int len = stones.length;
        if(stones[1] >= 2) return false;
        for(int i =0; i < len; i++){
            map.put(stones[i],i);
        }
        dfs(1,1,len);
        return flag;
    }
    public void dfs(int index, int k, int len){
        if(index == len-1){
            flag = true;
            return;
        }
        if(k == 1){
            if(stones[index]+1 <= stones[len-1]){
                if(map.containsKey(stones[index]+1)){
                    dfs(map.get(stones[index]+1),1,len);
                } 
            }
            if(stones[index]+2 <= stones[len-1]){
                if(map.containsKey(stones[index]+2)){
                    dfs(map.get(stones[index]+2),2,len);
                } 
            }
        }
        else{
            if(stones[index]+k-1 <= stones[len-1]){
                if(map.containsKey(stones[index]+k-1)){
                    dfs(map.get(stones[index]+k-1),k-1,len);
                } 
            }
            if(stones[index]+k <= stones[len-1]){
                if(map.containsKey(stones[index]+k)){
                    dfs(map.get(stones[index]+k),k,len);
                } 
            }
            if(stones[index]+k+1 <= stones[len-1]){
                if(map.containsKey(stones[index]+k+1)){
                    dfs(map.get(stones[index]+k+1),k+1,len);
                }
            }
        }   
    }
}

当然dfs是不能通过的

 

 记忆化搜索

class Solution {
    int[] stones;
    HashMap<Integer,Integer> map = new HashMap<>();
    boolean flag = false;
    boolean[][] memo;
    public boolean canCross(int[] stones) {
        this.stones = stones;
        int len = stones.length;
        if(stones[1] >= 2) return false;
        for(int i =0; i < len; i++){
            map.put(stones[i],i);
        }
        memo = new boolean[len][len+1];
        dfs(1,1,len);
        return flag;
    }
    public void dfs(int index, int k, int len){
        if(index == len-1){
            flag = true;
            return;
        }
        if(memo[index][k]) return;
        if(k == 1){
            if(map.containsKey(stones[index]+1)){
                dfs(map.get(stones[index]+1),1,len);
            } 
            if(map.containsKey(stones[index]+2)){
                dfs(map.get(stones[index]+2),2,len);
            } 
        }
        else{
            if(map.containsKey(stones[index]+k-1)){
                dfs(map.get(stones[index]+k-1),k-1,len);
            } 
            if(map.containsKey(stones[index]+k)){
                dfs(map.get(stones[index]+k),k,len);
            } 
            if(map.containsKey(stones[index]+k+1)){
                dfs(map.get(stones[index]+k+1),k+1,len);
            }
        }
        memo[index][k] = true;   
    }
}

实际上以上的解法可以看出很多代码是可以重复的,可以写成for循环,堆结果合并,并处理k=1的特殊情况。

Ref.[1]:

DFS

class Solution {
    Map<Integer, Integer> map = new HashMap<>();
    public boolean canCross(int[] ss) {
        int n = ss.length;
        // 将石子信息存入哈希表
        // 为了快速判断是否存在某块石子,以及快速查找某块石子所在下标
        for (int i = 0; i < n; i++) {
            map.put(ss[i], i);
        }
        // check first step
        // 根据题意,第一步是固定经过步长 1 到达第一块石子(下标为 1)
        if (!map.containsKey(1)) return false;
        return dfs(ss, ss.length, 1, 1);
    }

    /**
     * 判定是否能够跳到最后一块石子
     * @param ss 石子列表【不变】
     * @param n  石子列表长度【不变】
     * @param u  当前所在的石子的下标
     * @param k  上一次是经过多少步跳到当前位置的
     * @return 是否能跳到最后一块石子
     */
    boolean dfs(int[] ss, int n, int u, int k) {
        if (u == n - 1) return true;
        for (int i = -1; i <= 1; i++) {
            // 如果是原地踏步的话,直接跳过
            if (k + i == 0) continue;
            // 下一步的石子理论编号
            int next = ss[u] + k + i;
            // 如果存在下一步的石子,则跳转到下一步石子,并 DFS 下去
            if (map.containsKey(next)) {
                boolean cur = dfs(ss, n, map.get(next), k + i);
                if (cur) return true;
            }
        }
        return false;
    }
}

 记忆化搜索

class Solution {
    Map<Integer, Integer> map = new HashMap<>();
    // int[][] cache = new int[2009][2009];
    Map<String, Boolean> cache = new HashMap<>();
    public boolean canCross(int[] ss) {
        int n = ss.length;
        for (int i = 0; i < n; i++) {
            map.put(ss[i], i);
        }
        // check first step
        if (!map.containsKey(1)) return false;
        return dfs(ss, ss.length, 1, 1);
    }
    boolean dfs(int[] ss, int n, int u, int k) {
        String key = u + "_" + k;
        // if (cache[u][k] != 0) return cache[u][k] == 1;
        if (cache.containsKey(key)) return cache.get(key);
        if (u == n - 1) return true;
        for (int i = -1; i <= 1; i++) {
            if (k + i == 0) continue;
            int next = ss[u] + k + i;
            if (map.containsKey(next)) {
                boolean cur = dfs(ss, n, map.get(next), k + i);
                // cache[u][k] = cur ? 1 : -1;
                cache.put(key, cur);
                if (cur) return true;
            }
        }
        // cache[u][k] = -1;
        cache.put(key, false);
        return false;
    }
}

以上四种解答,两种写法,在本质上一样,枚举在一个位置可能的情况,进行递归,DFS都不能通过,使用记忆化搜索降低时间复杂度,走过的路不再走。 

动态规划 

class Solution {
    public boolean canCross(int[] ss) {
        int n = ss.length;
        // check first step
        if (ss[1] != 1) return false;
        boolean[][] f = new boolean[n + 1][n + 1];
        f[1][1] = true;
        for (int i = 2; i < n; i++) {
            for (int j = 1; j < i; j++) {
                int k = ss[i] - ss[j];
                // 我们知道从位置 j 到位置 i 是需要步长为 k 的跳跃

                // 而从位置 j 发起的跳跃最多不超过 j + 1
                // 因为每次跳跃,下标至少增加 1,而步长最多增加 1 
                if (k <= j + 1) {
                    f[i][k] = f[j][k - 1] || f[j][k] || f[j][k + 1];
                }
            }
        }
        for (int i = 1; i < n; i++) {
            if (f[n - 1][i]) return true;
        }
        return false;
    }
}

参考来源 Ref.

[1] leetcode 宫水三叶 【宫水三叶】一题四解 : 降低确定「记忆化容器大小」的思维难度

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/477413.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【HQL - 查询用户的累计消费金额及VIP等级】

水善利万物而不争&#xff0c;处众人之所恶&#xff0c;故几于道&#x1f4a6; 题目&#xff1a; 从订单信息表(order_info)中统计每个用户截止其每个下单日期的累积消费金额&#xff0c;以及每个用户在其每个下单日期的VIP等级。 用户vip等级根据累积消费金额计算&#xff0…

Unity之OpenXR+XR Interaction Toolkit基本配置

前言 XR Interaction Toolkit 是Unity基于OpenXR标准&#xff0c;发布的一套XR工具&#xff0c;目的是方便我们快速接入XR相关的SDK&#xff0c;并且做到兼容不同VR设备的目的&#xff0c;目前流行的VR设备如Oculus&#xff0c;Metal&#xff0c;HTC Vive&#xff0c;Pico等统…

JavaSE第三章 访问修饰符,Collection,List

这里写目录标题 一 访问修饰符二 集合1.1 数组1.2 集合1.3 读Collection的源码1.3.1 add添加方法1.3.2 clear&#xff0c;size&#xff0c;isEmpty方法1.3.3 remove 方法1.3.4 equals方法与contain方法1.3.5 遍历&#xff0c;迭代器或者增强for循环1.3.6 迭代器重点 1.4 List1.…

numpy的下载、数据类型、属性、数组创建

下载numpy 因为numpy不依赖于任何一个包所以numpy可以直接使用pip命令直接下载 下载命令&#xff1a; pip install numpy # 默认从https://pypi.org/simple 下载 pip install numpy -i https://pypi.tuna.tsinghua.edu.cn/simple/ # 从清华大学资源站点下载 pip install nump…

一步步解密微商城系统开发流程

作为现代商业的重要组成部分&#xff0c;电子商务系统的建设对于企业的发展至关重要。然而&#xff0c;对于一些小型企业来说&#xff0c;建设一个完整的电子商务系统可能会耗费大量的时间和金钱。微商城系统的出现有效地解决了这个问题&#xff0c;因为它可以在不需要太多资金…

趣说数据结构(练习1) —— 顺序表/链表力扣刷题

练习 1 —— 顺序表/链表力扣刷题 1. 合并两个有序链表 力扣题目地址&#xff1a;https://leetcode.cn/problems/merge-two-sorted-lists/ 问题描述&#xff1a;将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 示例&#x…

【云计算•云原生】4.云原生之什么是Kubernetes

文章目录 Kubernetes概念Kubernetes核心概念集群podConfigMap Kubernetes架构master节点的组件worker节点组件 Kubernetes网络架构内部网络外部网络 k8s各端口含义 Kubernetes概念 K8S就是Kubernetes&#xff0c;Kubernetes首字母为K&#xff0c;末尾为s&#xff0c;中间一共有…

全球5G市场最新进展及未来展望

从智慧医疗到万物互联&#xff0c;从无人驾驶到关乎我国未来发展的“新基建”&#xff0c;自2019年全球5G商用启动后&#xff0c;5G就步入了发展“快车道”;2022年继续保持快速稳定的增长态势&#xff0c;在网络建设、人口覆盖、终端形态等方面发展势头强劲&#xff0c;在技术标…

1695_week2_算法与函数(MIT使用Python编程学习手记)

全部学习汇总&#xff1a; GreyZhang/python_basic: My learning notes about python. (github.com) 首先说明一下&#xff0c;这部分信息的整理只是我个人的理解。由于自己的知识功底以及英语水准&#xff0c;很可能会有大量的疏漏。再此&#xff0c;我只想把自己学习时候的一…

C++(类和对象上篇)

本节目标&#xff1a; 1.面向过程和面向对象初步认识 2.类的引入 3.类的定义 4.类的访问限定符及封装 5.类的作用域 6.类的实例化 7.类的对象大小的计算 8.类成员函数的this指针 目录 1、面向过程和面向对象初步认识 2、类的引入 4.类的访问限定符及封装 4.1访问限定…

为了做低代码平台,这些年我们对.NET的DataGridView做的那些扩展

我们的低代码开发平台从一开始决定做的时候&#xff0c;就追求未来能够支持多种类型的客户端&#xff0c;目前支持Winform&#xff0c;Web&#xff0c;H5&#xff0c;FlutterAPP&#xff0c;当然了&#xff0c;未来也有可能会随着实际的需要淘汰掉一些客户端的。 为了系统更易…

springboot - 实现动态刷新配置

自定义的配置数据源&#xff0c;继承自Spring框架的 MapPropertySource 类&#xff0c;从一个名为 my.properties 的文件中读取配置信息&#xff0c;并在每10秒钟刷新一次。 这里不加Component&#xff0c;是因为&#xff1a; FilePropertiesSource filePropertiesSource new…

辨析 总结PMP各种BS结构

OBS 组织分解结构、BOM 物料清单、WBS工作分解结构、RBS 资源分解结构、RBS 风险分解结构、RAM 责任分匹配矩阵辨析 组织分解结构 OBS&#xff08;Organizational Breakdown Structure&#xff09; 描述了执行组织的层次结构&#xff0c;以便把工作包同执行组织单元相关联 物料…

电子邮件SDK:MailBee.NET 12.3.1 Crack

MailBee.NET 对象捆绑包包括SMTP、POP3、IMAP、EWS、安全、反垃圾邮件、Outlook 转换器、地址验证器、PDF组件以及BounceMail、HTML、MIME、ICalVCard组件&#xff0c;这些组件是一项常用功能。 MailBee.NET Objects是一组强大且功能丰富的 .NET 组件&#xff0c;用于创建、发送…

Haffman编码(算法导论)

上次算法导论课讲到了Haffman树&#xff0c;笔者惊叹于Haffman编码的压缩效果&#xff0c;故想自己亲自动手尝试写一个极简的Haffman压缩程序。 首先&#xff0c;我们来了解一下什么是Haffman编码 Haffman编码 赫夫曼编码可以很有效地压缩数据&#xff1a;通常可以节省20%&am…

UNIX环境高级编程——进程关系

9.1 引言 本章详细说明进程组以及会话的概念&#xff0c;还将介绍登录shell&#xff08;登录时所调用的&#xff09;和所有从登录shell启动的进程之间的关系。 9.2 终端登录 9.3 网络登录 9.4 进程组 每个进程除了有一进程ID之外&#xff0c;还属于一个进程组&#xff0c;进…

一曲微茫度余生 ——川剧《李亚仙》唱响香港西九戏曲中心

2023年4月28日晚&#xff0c;香港西九戏曲中心灯火辉煌。重庆市川剧院携手成都市川剧研究院带来的川剧《李亚仙》首场演出在这个为戏曲而设的世界级舞台重磅上演。 此次访演受香港西九戏曲文化中心的邀请&#xff0c;原重庆市文化和旅游发展委员会党委书记、主任刘旗带队&…

『LogDashboard』.NET开源的日志面板——登录授权页面扩展

&#x1f4e3;读完这篇文章里你能收获到 了解LogDashboard扩展开源项目——LogDashboard.Authorization掌握LogDashboard扩展内置登录页的使用方式 文章目录 一、LogDashbaord介绍1. 功能支持2. 快速开始 二、LogDashboard.Authorization1. 功能介绍2. 项目接入3. 更多示例 一…

Python语法学习

目录 Openmv用micro python开发的原因 print函数 列表 元组 判断 if...else... if...elif...else 循环 for循环 while循环 强制类型转换 点灯之路 点个不同颜色的闪烁LED 本文章仅作为个人的Openmv学习笔记&#xff0c;有问题欢迎指正~ Openmv用micro python开发…

【MPC|云储能】基于模型预测控制(MPC)的微电网调度优化的研究(matlab代码)

目录 1 主要内容 2 程序难点及问题说明 3 部分程序 4 下载链接 1 主要内容 该程序分为两部分&#xff0c;日前优化部分——该程序首先根据《电力系统云储能研究框架与基础模型》上面方法&#xff0c;根据每个居民的实际需要得到响应储能充放电功率&#xff0c;优化得到整体…