暴力递归转动态规划(一)

news2024/10/7 12:17:03

前两篇帖子介绍了暴力递归的过程,总的来说就是利用自然智慧+不断的尝试。这篇文章则会介绍如何将暴力递归转成动态规划。

斐波那契数列
斐波那契数列一定都不陌生,规定第一列的值是1,第二列的值是2的话,那第七列的值就是13,以此类推,整体上是一个 f(N) = f(N -1 ) + f(N - 2)。
在这里插入图片描述
那如果尝试着用暴力递归的思想来解决的话代码如下:

public static int f(int N) {
        if (N == 1) {
            return 1;
        }
        if (N == 2) {
            return 1;
        }
        return f(N - 1) + f(N - 2);
    }

这么简短的几行代码其实就实现了斐波那契数列数列,那如果此时要求f(7)的值,展开来看的话,其实是不是就是一个二叉树的形状。f(7)依赖着f(5)、f(6)。f(6)依赖着f(5)、f(4)以此类推。。。。
在这里插入图片描述
通过画图可以看出,想要求出一个f(7)的值,会依赖很多方法,而且有的方法会执行很多次,那如果此时有一张表结构,可以将之前的结果 “缓存” 起来,以后碰到了直接拿值。是不是就方便了很多,用 O ( N ) O(N) O(N)的时间复杂度就可以解决这个问题。
这个"缓存",就是动态规划。来一道具体的题目巩固一下:

题目
假设有排成一行的N个位置,记为1 ~ N,N >= 2 , 有一个机器人,
开始时,机器人在其中的start位置(start是1 ~ N 中间的一个)。
如果机器人当前在1位置,那下一步只能向右来到2的位置。
如果机器人在N位置,那下一步只能向左来到 N - 1位置。
如果在中间,则可以左右挪动。规定机器人必须走K步,最终来到target位置,(target也是1 ~ N中的一个)。
问机器人从start必须走K步,来到target位置,一共有多少种方法。
在这里插入图片描述
如果所示,假设当前起点在2,终点在4,共4个位置,一共走4步,共有几种方法可以走到:共三种

  1. 2 -> 1 -> 2 - >3 -> 4
  2. 2 -> 3 -> 4 -> 3 -> 4
  3. 2 -> 3 -> 2 -> 3 -> 4

暴力递归

  1. 根据题目分析,确认base case ,当step 为0,说明没有步数可以走, 就return。
  2. 如果cur = 1,那下一步只能向右走。
  3. 如果cur = N,那下一步只能向左走。
  4. 否则,在中间,则左右都可以,那向左走到达target的方法 + 向右走到达target的方法,就是总共的方法数。

代码
参数cur:当前机器人所在位置 step:剩余的步数 target:目标位置 N:一共N个位置
K:一共有多少步

 	//方法返回机器人从cur出发,走step步,到达target的方法数
    public static int ways(int cur, int K, int target, int N) {
        return process(cur, K, target, N);
    }

    public static int process(int cur, int step,int target, int N) {
    	//当步数为0时,看当前位置是否在目标位置,如果在,则方法数 + 1,否则认为没走到为0
        if (step == 0) {
            return cur == target ? 1 : 0;
        }
        //无论怎么走,每走一步 step 一定 -1
		//当前位置为1时,必须向右,所以下一步会是在2位置
        if (cur == 1) {
            return process(2, step - 1, target, N);
        }
        //当前位置为N时,必须向左,所以下一步在N -1位置
        if (cur == N) {
            return process(N - 1, step - 1, target, N);
        }
        //否则,在中间,可能向左走,也可能向右走
        return process(cur + 1, step - 1, target, N) + process(cur - 1, step - 1, target, N);
    }

优化
如何利用暴力递归优化成动态规划其中很重要的一点就是,根据调用过程,看有没有重复接,如果出现重复解的情况,则一定可以优化成动态规划
从上面的代码中可以分析出,影响ways方法结果的因素都有什么?
N是固定不变的,target也是固定不变的,而上面的代码中,是不是只有cur当前位置的变化以及step在不停的减少。所以,影响整个结果的参数是当前位置和步数。
将过程展开来看。比如说现在start = 7, target = 15,step = 10,从7位置出发走到15位置,要走10步,看看它都有什么走法。
在这里插入图片描述
7是中间位置,出发后,左右两边都可以走,当走到当前位置为7时,剩余步数是8,子过程有重复调用情况,说明可以由暴力递归优化成动态规划。此时不用在意之前的调用情况。
因为此时两个画圈地方的当前位置都是7,剩余步数是8,以及target是15是固定不变的,所以此时到target的方法数一定是一样的。所以不用在意是 7 -> 6 -> 7 还是 7- > 8 -> 7。
由此可以分析出,cur和step是决定状态的key,只要这两个确定了,那结果就可以确定了。
找到了key,根据key创建一个二维数组,来代表缓存的表。如果来到当前位置,并且剩余步数还一样,那到达target的方法数也一定相同,如果缓存表中存在,可直接通过缓存表获取即可。

代码

	//cur范围:0 ~ N
    //step范围:0 ~ K
    public static int ways2(int cur, int K, int target, int N) {
        //创建一个N + 1和 K + 1返回的数组,保证到任何位置都能囊括进去
        int[][] dp = new int[N + 1][K + 1];
        for (int i = 0; i <= N; i++) {
            for (int j = 0; j <= K; j++) {
                dp[i][j] = -1;
            }
        }
        return process2(cur, K, target, N, dp);
    }

    public static int process2(int cur, int step, int target, int N, int[][] dp) {
        //不等于 -1 说明 之前计算过当来到cur位置时,剩余step 到达target的方法数
        if (dp[cur][step] != -1) {
            return dp[cur][step];
        }
        //走到这,说明没算过
        int ans = 0;
        if (step == 0) {
            ans = cur == target ? 1 : 0;
        } else if (cur == 1) {
            ans = process2(2, step - 1, target, N, dp);
        } else if (cur == N) {
            ans = process2(N - 1, step - 1, target, N, dp);
        } else {
            ans = process2(cur + 1, step - 1, target, N, dp) + process2(cur - 1, step - 1, target, N, dp);
        }
        //记录当前位置到达target的方法数。
        dp[cur][step] = ans;
        return ans;
    }

此时,代码已经经历了"傻缓存"的优化,通过dp表来记录每一步的方法数,这种从顶向下的动态规划,名字叫做"记忆化搜索",本质上走每一个分支就是搜索,通过缓存表形成记忆化,利用空间换时间,通过一张缓存表来换取更大的时间。

如何将上面代码进行再次的优化?
上面通过cur当前所在位置以及剩余步数step构建了一个二维数组的缓存表,这个二维数组包含了给定的cur和step内每一步所有结果,那是不是可以尝试将这个dp表画出来?假设,当前机器人在2位置,总共5个位置,要走6步,目标是走到位置4。
所以cur = 2 K = 6 N =5 target = 4 ,画出来的图是这样的。

其中行是当前所在位置,列是步数,因为不可能到达0行,所以0行所在位置标记x。初始位置是2,还剩6步,标星星。
我只要将图补全,将当前位置和每一步的结果方法数填充到格子中。是不是就可以直接从表中知道2-6位置的步数。
在这里插入图片描述
那该如何完善这个表么?从最开始的暴力递归代码做起,有一些base case会直接给我们答案。根据代码逻辑一行一行的完善。

 public static int process(int cur, int step, int target, int N) {
        if (step == 0) {
            return cur == target ? 1 : 0;
        }

        if (cur == 1) {
            return process(2, step - 1, target, N);
        }
        if (cur == N) {
            return process(N - 1, step - 1, target, N);
        }
        return process(cur + 1, step - 1, target, N) + process(cur - 1, step - 1, target, N);
    }

先从base case来,如果我剩余步数为0,此时如果 cur 的位置在 target 上的话,是不是说明有一种方法是可以到达的,其余不在target位置上方法数就是0,所以第一列就出来了。
在这里插入图片描述
继续接着往下看,当前是cur、step的状态(参数传进来的)那如果当前位置 cur = 1,根据题意,下一步只能向2位置挪动,而每移动一次step - 1,所以如果此时是cur = 1,step = 1的情况下,依赖的是cur = 2,step = 0的结果。所以cur在第一行任意位置依赖关系是这样的。
在这里插入图片描述
继续往下,如果当前cur = N,那下一步只能向左移动(N -1),同样每移动一次step - 1,所以cur在N行任意位置的依赖关系是这样的。

好,现在只剩普遍位置了,假设此时 cur = 3,看代码,可以左移也可以右移。所以此时的依赖关系是这样的。
在这里插入图片描述
在根据代码,如果此时cur = 3,是不是将依赖结果进行相加,那此时dp表是不是就完整了。
在这里插入图片描述
可以看到,cur = 2,step = 6时,一共13个方法解可到达target = 4的位置。此时如果将dp表直接构建出来,是不是就可以根据变量cur和step来直接获取到结果。

代码
整个动态规划代码很简洁。

public static int ways3(int cur, int K, int target, int N) {
        int[][] dp = new int[N + 1][K + 1];
		//先将对应target位置标1
		//初始化int[][]默认值都是0,所以其他值不用管
        dp[target][0] = 1;
		//按列遍历
        for (int step = 1; step <= K; step++) {
        	//先将第一列的值确定
            dp[1][step] = dp[2][step - 1];
            //因为我单独遍历N行时的值,所以这个遍历到 N -1即可
            for (int j = 2; j < N; j++) {
            	//按照依赖关系,当前位置依赖左上和左下的位置
                dp[j][step] = dp[j - 1][step - 1] + dp[j + 1][step - 1];
            }
            //再将最后一列的值确定
            dp[N][step] = dp[N - 1][step - 1];
        }

        return dp[cur][K];
    }

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

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

相关文章

2023Win11安装Oracle19c教程

2023Win11安装Oracle19c教程 一、下载安装二、安装三、配置四、navicat连接oracle 一、下载安装 进入官网&#xff0c;选择产品-Oracle DataBase&#xff0c;点击进入下载界面 点击跳转下载 选择19c进行下载 选择windows64位版本下载 登录账号后开始下载&#xff0c;等待下载…

1.3.1背包模型(一)

01背包 划分依据&#xff1a;依靠“最后一步”来划分 完全背包 多重背包 完全背包&#xff1a;求所有前缀的最大值 多重背包&#xff1a;求滑动窗口内的最大值 1.多重背包问题 III 有 N N N种物品和一个容量是 V V V的背包。 第 i i i种物品最多有 s i s_{i} si​件&…

激活函数总结(二十二):激活函数补充(Soft Exponential、ParametricLinear)

激活函数总结&#xff08;二十二&#xff09;&#xff1a;激活函数补充 1 引言2 激活函数2.1 Soft Exponential激活函数2.2 ParametricLinear激活函数 3. 总结 1 引言 在前面的文章中已经介绍了介绍了一系列激活函数 (Sigmoid、Tanh、ReLU、Leaky ReLU、PReLU、Swish、ELU、SE…

机器人制作开源方案 | 桌面级机械臂--仿真设计

1. Ros概述 ROS是一个适用于机器人编程的框架&#xff0c;这个框架把原本松散的零部件耦合在了一起&#xff0c;为它们提供了通信架构。ROS虽然叫做操作系统&#xff0c;但并非Windows、Mac那样通常意义的操作系统&#xff0c;它只是连接了操作系统和你开发的ROS应用程序&#…

AODV代码实现详解——原理与源码分析(一)

首先来几个标准参考&#xff1a; RFC 3561 RFC 3561 中文翻译 一个博客 挺好的另一个博客 事件&#xff1f; 字段长度&#xff1f; 事件驱动 各种定时器 状态转移图&#xff1f; AODV协议 基本概念 AODV&#xff08;Ad hoc On-Demand Distance Vector&#xff09;是一种基于…

Flutter问题记录 - Unable to find bundled Java version

新版本的Android Studio真的移除了JRE&#xff0c;jre目录找不到&#xff0c;怪不得报错了&#xff0c;不过多了一个jbr目录&#xff0c;找了个以前的Android Studio版本对比 搜了一下jbr&#xff08;JetBrains Runtime&#xff09;&#xff0c;原来IDEA老早就开始用了&#xf…

Redis 7 教程 数据持久化

总体 RDB 介绍 RDB 持久化以指定的时间间隔执行数据集的时间点快照 。 把某一时刻的数据和状态以文件的形式写到磁盘上,即使出现故障宕机,快照文件也不会丢失,数据的可靠性得到保证。快照文件就是RDB(Redis DataBase)文件(dump.rdb) 作用 在指定的时间间隔内将内存中的数…

财务数据分析怎么做?看看奥威BI数据可视化工具的解法

从以往的BI智能数据可视化分析项目来看&#xff0c;要想快刀砍乱麻地做好财务数据分析&#xff0c;为企业运营决策提供更加直观深入的数据支持&#xff0c;那就需要为财务数据分析做好数据导入、建模、报表制作、展示等多方面的准备。奥威BI数据可视化工具为此特意打造了一套标…

C#实战:基于腾讯OCR技术实现企业证书识别和数据提取实践

一、OCR技术介绍 在当今数字化时代&#xff0c;OCR&#xff08;Optical Character Recognition&#xff09;识别技术正发挥着越来越重要的作用。OCR技术通过将图像中的文字转化为可编辑的文本形式&#xff0c;实现了对大量纸质文档的数字化处理和信息提取。常见的有企业资质证…

【uniapp】 实现公共弹窗的封装以及调用

图例&#xff1a;红框区域为 “ 内容区域 ” 一、组件 <!-- 弹窗组件 --> <template> <view class"add_popup" v-if"person.isShowPopup"><view class"popup_cont" :style"{width:props.width&&props.width&…

【VLDB 2023】基于预测的云资源弹性伸缩框架MagicScaler,实现“高QoS,低成本”双丰收

开篇 近日&#xff0c;由阿里云计算平台大数据基础工程技术团队主导&#xff0c;与计算平台MaxCompute团队、华东师范大学数据科学与工程学院、达摩院合作&#xff0c;基于预测的云计算平台资源弹性伸缩框架论文《MagicScaler: Uncertainty-aware, Predictive Autoscaling 》被…

解锁市场进入成功:GTM 策略和即用型示例

在最初的几年里&#xff0c;创办一家初创公司可能会充满挑战。根据美国小企业管理局的数据&#xff0c;大约三分之二的新成立企业存活了两年&#xff0c;几乎一半的企业存活了五年以上。导致创业失败的因素有市场需求缺失、资金短缺、团队不合适、成本问题等。由此&#xff0c;…

Flutter可执行屏幕动画的AnimateView

1.让动画使用起来就像使用widget。 2.可自定义动画。 3.内置平移动画。 演示&#xff1a; 代码: import dart:math; import package:flutter/cupertino.dart;class AnimateView extends StatefulWidget {///子Widgetfinal Widget child;///动画自定义final IAnimate? anim…

什么,一条指令直接黑了数据库!

什么&#xff0c;一条指令直接黑了数据库&#xff01; shigen最近研究了一下一款渗透工具sqlMap。它一款流行的开源工具&#xff0c;用于自动化SQL注入攻击和渗透测试。它专门设计用于检测和利用Web应用程序中的SQL注入漏洞。SQLMap具有丰富的功能集&#xff0c;可自动检测和利…

工厂方法模式的概述和使用

目录 一、工厂方法模式概述1. 定义2. 使用动机 二、工厂方法模式结构1. 模式结构2. 时序图 三、工厂方法模式的使用实例四、工厂方法模式的优缺点五、工厂方法模式在Java中应用 原文链接 一、工厂方法模式概述 1. 定义 工厂方法模式(Factory Method Pattern)又称为工厂模式&…

高通面临难题,Oryon核心存在问题,高通8cx Gen 4芯片将推迟发布

"高通公司面临难题&#xff0c;可能会导致骁龙8cx Gen 4的发布时间推迟"&#xff0c;关于骁龙8cx Gen 4处理器&#xff0c;还有一些其他值得关注的特点和功能。首先&#xff0c;据悉&#xff0c;骁龙8cx Gen 4采用了高通自家研发的Oryon核心架构&#xff0c;这是一项…

春秋云镜 Brute4Road

flag1 fscan扫描发现&#xff0c;6379开放ftp可以匿名登录 这里直接尝试了去打redis但是只有主从复制能成功&#xff08;这里应该是靶场有设置吧&#xff0c;对6379操作过后再次操作就会显示端口拒绝访问直接重置就可以了&#xff09; 之后用脚本一把梭哈即可获得shell #更改…

C++ list模拟实现

list模拟实现代码&#xff1a; namespace djx {template<class T>struct list_node{T _data;list_node<T>* _prev;list_node<T>* _next;list_node(const T& x T()):_data(x),_prev(nullptr),_next(nullptr){}};template<class T,class Ref,class Pt…

好用的网页制作工具就是这6个,快点来看!

对于网页设计师来说&#xff0c;好用的网页设计工具是非常重要的&#xff0c;今天本文收集了6个好用的网页设计工具供设计师自由挑选使用。在这6个好用的网页设计工具的帮助下&#xff0c;设计师将获得更高的工作效率和更精致的网页设计效果&#xff0c;接下来&#xff0c;就一…

Android studio APK切换多个摄像头(Camera2)

1.先设置camera的权限 <uses-permission android:name"android.permission.CAMERA" /> 2.布局 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"and…