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

一.跳跃游戏简单介绍

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 宫水三叶 【宫水三叶】一题四解 : 降低确定「记忆化容器大小」的思维难度

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

[入门必看]数据结构5.2:二叉树的概念

[入门必看]数据结构5.2&#xff1a;二叉树的概念 第五章 树与二叉树5.2 二叉树的概念知识总览5.2.1_1 二叉树的定义和基本术语5.2.1_2 二叉树的性质5.2.2 二叉树的存储结构 5.2.1_1 二叉树的定义和基本术语二叉树的基本概念二叉树的五种状态几个特殊的二叉树满二叉树&#xff1…

Navicat安装教程和评测

Navicat是一款功能强大的数据库管理软件&#xff0c;拥有丰富的功能和易于使用的界面&#xff0c;因此价格相对较高。此外&#xff0c;Navicat还提供了多种数据库类型的支持&#xff0c;包括MySQL、Oracle、PostgreSQL等&#xff0c;每种数据库类型都需要花费开发人员大量的时间…

飞利浦水健康携净水新品重磅亮相AWE2023

2023年度中国家电及消费电子博览会&#xff08;AWE2023&#xff09;于4月27日在上海新国际博览中心正式开幕。其中&#xff0c;飞利浦水健康携全屋高阶净水G5系列、厨下净水器U22Pro、冰热矿净四合一台式净饮机等新品悉数亮相&#xff0c;在暌违2年的AWE舞台上&#xff0c;为行…

小程序按钮重复点击解决方案

文章目录 前言一、为什么会发生重复点击二、针对以上问题怎么处理1、分析解决方法&#xff1a;1. 反馈2.禁用 三、最优解决总结 前言 小程序是直面用户便捷的应用&#xff0c;而在用户使用时往往都会涉及到关键节点的按钮点击&#xff0c;例如&#xff0c;注册登录时&#xff…

【技术】《Netty》从零开始学netty源码(四十八)之缓存池ObjectPool

目录 ObjectPool创建对象池获取对象get()从本地池中获取对象claim()回收对象 ObjectPool 在分析PooledByteBuf的时候我们遇到了recycleHandler类&#xff0c;该类用于回收已经使用完毕的缓存对象并将其放回池中供下次循环利用&#xff0c;Netty的对象池工作过程大体如下&#…

vulnhub靶机Misdirection

环境准备 下载链接&#xff1a;https://download.vulnhub.com/misdirection/Misdirection.zip 解压后双击ovf文件导入虚拟机 网络&#xff1a;DHCP、NAT、192.168.100.0/24网段 信息收集 主机发现 192.168.100.133是新增的ip 端口扫描 发现开放了以上端口&#xff0c;继续…

ChatGPT之父:未训练GPT-5

GPT等大型语言模型带动的芯片需求飙升趋势依然没有平息的迹象&#xff0c;英伟达的最新版旗舰AI芯片H100近日在网上的售价已经被炒到4万多美金&#xff0c;反映了科技行业对训练和部署人工智能软件的需求仍未被满足。 一、商业圈 1.马斯克成立新AI公司硬刚OpenAI 当地时间4月…

c++ 11标准模板(STL) std::vector (二)

定义于头文件 <vector> template< class T, class Allocator std::allocator<T> > class vector;(1)namespace pmr { template <class T> using vector std::vector<T, std::pmr::polymorphic_allocator<T>>; }(2)(C17…

如何使用递归函数实现Excel列号转换列标

在Excel中&#xff0c;列标与列号转换是VBA开发过程中经常用到的功能&#xff0c;下面这篇博客为大家解释了多种方法。 【Excel列标与列号转换】 那么这篇博文的核心是“递归过程”&#xff0c;实现这个功能并不是必须使用递归过程&#xff0c;但是这也不失为一种实现方法&am…

JavaScript实现输入圆的半径,输出周长、体积和面积的代码

以下为输入圆的半径,输出周长、体积和面积实现结果的代码和运行截图 目录 前言 一、请输入圆的半径,输出周长、体积和面积 1.1运行流程及思想 1.2代码段 1.3 JavaScript语句代码 1.4运行截图 前言 1.若有选择&#xff0c;您可以在目录里进行快速查找&#xff1b; 2.本博…

03 KVM虚拟机镜像制作

文章目录 03 KVM虚拟机镜像制作3.1 概述3.2 制作镜像3.2.1 使用root用户安装qemu-img软件包3.2.2 使用qemu-img工具的创建镜像文件 3.3 修改镜像磁盘空间大小3.3.1 查询当前虚拟机镜像磁盘空间大小3.3.2 修改镜像磁盘空间大小3.3.3 查询修改后的镜像磁盘空间大小 03 KVM虚拟机镜…

【HTML 标签详解】

&#x1f389;&#x1f389;&#x1f389;点进来你就是我的人了博主主页&#xff1a;&#x1f648;&#x1f648;&#x1f648;戳一戳,欢迎大佬指点! 欢迎志同道合的朋友一起加油喔&#x1f9be;&#x1f9be;&#x1f9be; 目录 1. HTML结构 1.1 HTML 基本结构 1.2 标签层…

DBeaver 没有菜单项 生成SQL Generate SQL

文章目录 Intro问题的根本有无该菜单项取决于你的查询SQL是单表还是多表&#xff1f;单表查询的结果集的菜单多表关联查询的结果集的菜单 测试版本 Intro DBeaver 是一款很棒的多平台、支持多数据源的GUI数据库客户端。 有一个我经常使用的功能就是&#xff1a; 当我查询到一个…

Linux内核阅读自学精简教程目录(必读)

学习Linux内核需要一定的计算机基础知识&#xff0c;包括操作系统&#xff0c;计算机网络等。 以下是学习Linux内核的步骤&#xff1a; 了解Linux内核的基本概念和架构&#xff0c;学习Linux内核源代码的组成和结构。学习C语言和汇编语言&#xff0c;这是深入理解Linux内核的…

界面控件DevExpress WinForm的垂直网格,让数据展示更灵活(二)

DevExpress WinForm Vertical Grid&#xff08;垂直网格&#xff09;组件设计用于提供UI灵活性&#xff0c;它允许显示数据集中的单个行&#xff0c;或在其90度反向网格容器中显示多个数据集行。此外&#xff0c;开发者还可以将其用作属性网格&#xff0c;就像在Visual Studio …

Python使用pytorch深度学习框架构造Transformer神经网络模型预测红酒分类例子

1、红酒数据介绍 经典的红酒分类数据集是指UCI机器学习库中的Wine数据集。该数据集包含178个样本&#xff0c;每个样本有13个特征&#xff0c;可以用于分类任务。 具体每个字段的含义如下&#xff1a; alcohol&#xff1a;酒精含量百分比 malic_acid&#xff1a;苹果酸含量&a…

用手机APP操作使用井用采样器更省时省力

井用采样器的主要功能特点就是&#xff1a;机身小巧&#xff0c;方便操作。可用于井下作业&#xff0c;手机APP可实时查看采样数据&#xff0c;节省人力。 利用自动采样器进行水样采集可以说节省很大的人力物力&#xff0c;但是有时为了采到更具代表性的水样&#xff0c;我们需…

JAVA-6-[Spring框架]Bean的作用域和生命周期

1 Spring Bean 1、Spring有两种类型bean&#xff0c;一种普通bean&#xff0c;另外一种工厂bean(FactoryBean)。 2、普通bean&#xff1a;在配置文件中定义的bean类型就是返回的类型。 3、工厂bean&#xff1a;在配置文件中定义的bean类型可以和返回类型不一样。 第一步 创建类…

React超级简单易懂全面的有关问题回答(面试)

目录 React事件机制&#xff1a; 2、React的事件和普通的HTML有什么不同&#xff1a; - 事件命名的规则不同&#xff0c;原生事件采用全小写&#xff0c;react事件采用小驼峰 3、React组件中怎么做事件代理&#xff1f;他的原理是什么&#xff1f; 4、React高阶组件、Rend…

mysql 如何避免索引失效

案例演示 建表及初始化数据 CREATE TABLE staffs (id INT PRIMARY KEY AUTO_INCREMENT,NAME VARCHAR(24) NOT NULL DEFAULT ,age INT NOT NULL DEFAULT 0,pos VARCHAR(20) NOT NULL DEFAULT ,#职位add_time TIMESTAMP NOT NULL DEFAULT CURREN…