【Hello Algorithm】暴力递归到动态规划(一)(上)

简介: 【Hello Algorithm】暴力递归到动态规划(一)

我们可以一句话总结下动态规划

动态规划本质是一种以空间换时间的行为 如果你发现有重复调用的过程 在经过一次之后把结果记下来 下次调用的时候直接用 这就是动态规划

斐波那契数列的动态规划

一般来说我们可以使用递归来解决斐波那契数列问题 代码如下

int fib(int n)    
{    
  if (n <= 0)    
  {    
    cerr << "error" << endl;    
  }    
  if (n <= 2)    
  {    
    return 1;    
  }    
  return fib(n-1) + fib(n-2);    
}    

当然 这种方式会产生大量的重复计算 所以说我们可以保存上个和上上个的计算值来进行动态规划

int dpfib(int n)    
{    
  if (n <= 2)    
  {    
    return 1;    
  }    
  int i = 1;    
  int j = 1;    
  int k = 0;    
  while (n-2)    
  {    
    k = i + j;    
    i = j;    
    j = k;     
    n--;    
  }    
  return k;    
}     

这样子写代码就能避免大量的重复计算了

机器人走路

初级递归

假设现在有1~N个位置

b1d007b2b93e4cdca40c0ff3a0de45b8.png有一个小机器人 现在在START位置

2b7c68559b1f4b0a97a872f1ae907939.png它现在要去aim位置 (aim为1~N上的随机一点) 能走K步 (K >= 0)

每次只能走一步 不能越界 不能停止 现在请问有多少种方式能走走到

现在假设 S = 2 N = 5 AIM = 4 K = 6

我们一开始写出的递归方程如下

int process1(int cur , int rest , int aim , int k)

参数含义如下

  • cur 当前位置
  • rest 剩余步数
  • aim 目标地点
  • k 能走的步数

base case为

  • 如果剩余步数为0 则判断cur是否为aim地点

否则我们执行递归继续往下走

int process1(int cur , int rest , int aim , int n)
{                             
  if (rest == 0)
  {
    return cur == aim ? 1 : 0;
  }
  if (cur == 1)
  {
    return process1(2 , rest -1 , aim , n);
  }
  if (cur == n)
  {
    return process1(n-1 , rest-1 , aim , n);
  }                                      
  return process1(cur-1 , rest-1 , aim , n)
    + process1(cur+1 , rest-1 , aim , n);                                                                                       
}  

初级动态规划

现在我们要进行进一步的动态规划

我们想想看在递归函数中有真正决定的是哪两个参数

我们每次传递参数的时候 aimn 是不变的

其实每次变化的就是 currest

a2a3c2a565134e8fb2757fe47ff4319c.png

我们可以将该函数往下推演 我们会发现会出现两个相同的结果

如果继续往下展开的话则肯定会有重复的部分 所以说我们最好能将这些函数的结果记录下来 避免重复计算

我们选择使用一个二维数组存储每个函数的计算结果

 vector<vector<int>> dp(n + 1 , vector<int>(rest + 1));    
  for (int i = 0 ; i < n + 1 ; i++ )    
  {    
    for (int j = 0; j < rest + 1 ; j++)                                                                                         
    {    
      dp[i][j] = -1;    
    }    
  }    

这个二维数组 i j 分别标识当前位置和剩余步数

该数组的值表示在当前位置下走剩余步数能走到目的地有多少种解法

我们首先全部初始化为-1

int _process2(int cur , int rest , int aim , int n , vector<vector<int>>& dp)
{
  if (dp[cur][rest] != -1)
  {
    return dp[cur][rest];
  }
  int ans = 0;
  if (rest == 0)
  {
    ans = cur == aim ? 1 : 0;
  }
  else if (cur == 1)
  {
    ans = _process2(2 , rest-1 , aim , n , dp);
  }
  else if (cur == n)
  {
    ans = _process2(n-1 , rest-1 , aim , n , dp);
  }                                                                                                                             
  else 
  {
    ans = _process2(cur -1  , rest -1 , aim , n , dp ) 
      + _process2(cur +1 , rest -1 , aim , n , dp);    
  }    
  dp[cur][rest] = ans;
  return ans;
 }

之后在我们的函数中 如果数组中有结果 我们就直接返回 如果没有结果我们就将结果记录在数组中后返回 也能得到一样的结果

动态规划

我们以cur为横坐标 rest为纵坐标画一个图 并且将cur为0的时候值填入图中

9b00ad34282e4087b93d8e68d1faba14.png当cur为1的时候 我们回顾下我们的代码 我们会发现 此时该格上的数字只依赖于 dp[2][rest-1]

当cur为n的时候 我们回顾下之前的带啊吗 我们会发现 此时该格上的数字只依赖于 dp[n-1][rest-1]

当cur介于两者之间的时候 此时该格上的数字依赖于两种路径

dp[cur-1][rest-1] + dp[cur+1][rest-1]

0d5470fcfef440bf88250ee3596ff97b.png

那么我们既然有了第一列的数字 我们就可以推出整个dp数组的数值

从而我们就能得出 当前为start 还有k步要走的时候 我们有几种路径

代码表示如下

int process3(int cur , int rest , int aim , int n)
{
  vector<vector<int>> dp(n + 1 , vector<int>(rest + 1));    
  for (int i = 0 ; i < n + 1 ; i++ )    
  {    
    for (int j = 0; j < rest + 1 ; j++)    
    {    
      dp[i][j] = 0;    
    }    
  }    
  // row 1    
  dp[aim][0] = 1;    
  for (int r = 1 ; r <= rest ; r++)    
  {    
    dp[1][r] = dp[2][r-1];    
    for (int c = 2 ; c < n ; c++)                                                                                               
    {    
      dp[c][r] = dp[c-1][r-1] + dp[c+1][r-1];    
    }    
    dp[n][r] = dp[n-1][r-1];    
  }    
  return dp[cur][rest];   
} 

这就是完整的解决动态规划问题的步骤

相关文章
|
消息中间件 Linux 数据安全/隐私保护
linux mq的安装并设置开机启动 图文!!
linux mq的安装并设置开机启动 图文!!
484 0
|
8月前
|
数据采集 JSON 测试技术
如何在Python中高效实现CSV到JSON的数据转换
在实际项目中,数据格式转换是常见问题,尤其从CSV到JSON的转换。本文深入探讨了多种转换方法,涵盖Python基础实现、数据预处理、错误处理、性能优化及调试验证技巧。通过分块处理、并行处理等手段提升大文件转换效率,并介绍如何封装为命令行工具或Web API,实现自动化批量处理。关键点包括基础实现、数据清洗、异常捕获、性能优化和单元测试,确保转换流程稳定高效。
398 83
|
前端开发
定义CSS样式
定义CSS样式。
179 2
|
8月前
|
数据采集 数据安全/隐私保护 Python
从零开始:用Python爬取网站的汽车品牌和价格数据
在现代化办公室中,工程师小李和产品经理小张讨论如何获取懂车帝网站的汽车品牌和价格数据。小李提出使用Python编写爬虫,并通过亿牛云爬虫代理避免被封禁。代码实现包括设置代理、请求头、解析网页内容、多线程爬取等步骤,确保高效且稳定地抓取数据。小张表示理解并准备按照指导操作。
318 6
从零开始:用Python爬取网站的汽车品牌和价格数据
|
8月前
|
数据采集 并行计算 数据可视化
Pandas高级数据处理:数据报告生成实战指南
数据报告生成面临数据质量、计算性能、呈现形式和自动化等核心挑战。常见问题包括缺失值导致统计失真、内存溢出及可视化困难。解决方案涵盖数据清洗、分块处理、安全绘图模板等。通过模块化设计、异常处理机制和性能优化策略,如使用`category`类型、并行计算等,可大幅提升效率。最佳实践建议建立数据质量检查清单、版本控制和自动化测试框架,确保系统具备自适应能力,提升报告生成效率300%以上。
173 12
|
10月前
|
人工智能 自然语言处理 运维
阿里云多模态数据信息提取技术解决方案评测
阿里云多模态数据信息提取技术解决方案,利用先进AI技术处理文本、图像、音频和视频,帮助企业从海量数据中高效提取有价值信息。方案涵盖文本、图片、视频信息提取,适用于电商平台、安防等领域。通过大模型支持自动扩展与持续训练,提供简单部署及免费试用,评测显示其在识别准确性和易用性方面表现出色,但仍需优化高级设置提示和加载速度。
|
人工智能 算法 安全
人工智能伦理与监管:构建负责任的AI未来
【10月更文挑战第3天】随着人工智能(AI)技术的快速发展,其在社会各领域的应用日益广泛。然而,AI的广泛应用也带来了一系列伦理和监管挑战。本文旨在探讨AI的伦理问题,分析现有的监管框架,并提出构建负责任AI未来的建议。同时,本文将提供代码示例,展示如何在实践中应用这些原则。
1740 1
|
SQL 关系型数据库 MySQL
Go语言项目高效对接SQL数据库:实践技巧与方法
在Go语言项目中,与SQL数据库进行对接是一项基础且重要的任务
236 11
|
12月前
|
Java Nacos 微服务
微服务中间件之Nacos
Nacos是阿里巴巴开源的动态服务发现、配置管理和服务管理平台,支持服务注册与发现、配置管理及服务健康监测。采用Spring Cloud、Spring Boot、Raft算法等技术,适用于微服务架构和云原生应用,提供简单易用的安装部署方式和丰富的应用场景。
2175 4
|
安全 网络安全 Android开发
深度解析:利用Universal Links与Android App Links实现无缝网页至应用跳转的安全考量
【10月更文挑战第2天】在移动互联网时代,用户经常需要从网页无缝跳转到移动应用中。这种跳转不仅需要提供流畅的用户体验,还要确保安全性。本文将深入探讨如何利用Universal Links(仅限于iOS)和Android App Links技术实现这一目标,并分析其安全性。
1465 0