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

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

死亡概率问题

现在给定你三个参数 N M K

怪兽有 N 滴血 等着英雄来砍自己

英雄每一次打击会让怪兽流失 0 ~ M 滴血 概率相同

请求在K次之后 英雄把怪兽砍死的概率

递归版本

面对这个问题 我们首先来想递归函数怎么写

首先是返回值 我们无法直接返回一个概率给上层 所以我们这里决定返回怪兽死去的次数 只要上层用所有的可能性相除就能得到概率了

接下来就是将相关的参数填到函数中 因为怪兽剩余的血量是变化的 所以说我们不使用N作为参数 而是使用rest – 怪兽的剩余血量作为参数

int process(int rest , int K , int M)

我们首先来想base case

如果剩余血量为0 如果砍的刀数等于0此时都会结束

  if (rest <= 0)                                                                                                           
  {                                                                                                                        
    return pow(static_cast<double>(M + 1) , K);                                                                            
  }                                                                                                                        
  if (K == 0)    
  {                                                                                                                                         
    return rest <= 0 ? 1 : 0;    
  }    

否则我们就要开始考虑其他可能性了

因为每一刀可能会砍0~M滴血 所以说一共会有M+1种可能 我们全部加起来就能得到最终结果

  int ways = 0;
  for (int i = 0; i <= M; i++)
  {
    ways += process(rest - i , K - 1 , M);
  }
  return ways;

动态规划

接下来我们开始改写动态规划

首先我们观察到 可变参数有两个 血量和砍的次数

血量的变化范围是0~N+1 (其实可能会小于0 但是小于0我们能计算出来)

砍的次数的变化范围是0~K+1

我们将血量命名为hp 砍的次数命名为K+1

代码表示如下

int dp_process( int N , int K , int M)  
{  
  vector<vector<int>> dp(K + 1 , vector<int>(N + 1 , 0));  
  for (int i = 0; i <= K; i++)  
  {  
    dp[i][0] = pow(static_cast<double>(M+1) ,static_cast<double>(K));  
  }  
  for (int times = 1; times <= K; times++)  
  {  
    for(int hp = 1; hp <= N; hp++)  
    {  
       int ways = 0;  
       for (int i = 0; i<= M; i++)  
       {                    
         if (hp - i >= 0)  
         {  
           ways += dp[times - 1][hp - i];  
         }  
         else  
         {  
           ways += dp[times-1][0];  
         }  
       }  
       dp[times][hp] = ways;  
    }  
  }  
  return dp[K][M];  
  }

钱包问题四

arr是面值数组 其中的值都是正数且没有重复 给定一个正整数aim 返回组成aim的最小货币张数

我们首先来想base case

如果剩余的钱数小于0 那么我们无法找到最小张数

如果数组下标超过了arr 此时我们就需要分情况讨论

  • 如果此时rest为0 则0张就可以解决
  • 如果此时resr不为0 则无解

接下来我们就可以开始列可能性

我们可以选择0 ~ N张arr[index]的货币 之后将剩余的钱和下标交给下面的位置就可以

const int MAX_VAL = INT32_MAX;    
int process(vector<int>& arr , int index , int rest)    
{    
  if (rest < 0)    
  {    
    return MAX_VAL;    
  }    
  if (index == static_cast<int>(arr.size()))    
  {    
    return rest == 0 ? 0 : MAX_VAL;    
  }    
  int ways = MAX_VAL;    
  for (int fix = 0; fix * arr[index] <= rest; fix++)    
  {    
    int Next =  process(arr , index + 1 , rest - fix*arr[index]);    
    if(Next != MAX_VAL)    
    {    
      ways = min(ways , Next + fix);                                                                                                        
    }    
  }    
  return ways;    
} 

动态规划

我们可以看到两个变化的量是index 和 rest

index的变化范围是 0 ~ arr的大小

rest的范围是 0 ~ aim

于是我们可以围绕着代码建立一个二维表

之后按照上面的递归代码改写即可 代码表示如下

int dp_process(vector<int>& arr ,  int aim)
{
  int N = static_cast<int>(arr.size()) ; // N = arr.size() 
  vector<vector<int>> dp(N + 1 , vector<int>(aim + 1 , MAX_VAL));
  // base case 
  // 1. rest < 0 ?  -1 
  // 
  dp[N][0] = 0;
  for (int index = N -1 ; index >= 0 ; index--)                                                                                             
  {
    for(int rest = 0; rest <= aim; rest++)
    {
        int ways = MAX_VAL;
        for (int fix = 0; fix * arr[index] <= rest; fix++)
        {
          int Next =  dp[index + 1][rest - fix*arr[index]];
          if(Next != MAX_VAL)
          {
            ways = min(ways , Next + fix);
          }
        }
        dp[index][rest] = ways;
    }
  }
  return dp[0][aim];
}


相关文章
信道建模流程 | 带你读《大规模天线波束赋形技术原理与设计 》之二十八
本节将详细介绍衰落信道的整体建模流程,内容上与 3D 信道模 型 3GPP TR36.873 7.3 节和 3GPP TR38.901 的 7.5 节对应。两者在内容上大体相同,前者的目标为6GHz以下的信道建模(记为模型1),后者为0.5~100GHz 的信道建模(记为模型 2)。对于 6GHz 以下的信道建模,两者均可以使用, 在下文的描述中,两者不同的地方均会列出。
信道建模流程  | 带你读《大规模天线波束赋形技术原理与设计 》之二十八
|
存储 机器学习/深度学习 分布式计算
【DSW Gallery】COMMON_IO使用指南
COMMON_IO模块提供了TableReader和TableWriter两个接口,使用TableReader可以读取ODPS Table中的数据,使用TableWriter可以将数据写入ODPS Table。
【DSW Gallery】COMMON_IO使用指南
|
11月前
|
人工智能 自然语言处理 关系型数据库
阿里云云原生数据仓库 AnalyticDB PostgreSQL 版已完成和开源LLMOps平台Dify官方集成
近日,阿里云云原生数据仓库 AnalyticDB PostgreSQL 版已完成和开源LLMOps平台Dify官方集成。
|
Java 开发者 UED
【揭秘Java编程新境界】事件驱动:如何在Java中捕捉每一个关键瞬间?
【8月更文挑战第30天】事件驱动编程是一种编程范式,使程序能在事件发生时响应,而非按严格顺序执行。本文介绍Java中的事件驱动编程,包括基本概念、优势及其实现方法。通过事件监听器和事件对象,Java能够高效处理GUI、网络编程和游戏开发中的各种事件。文中还提供了创建事件监听器、自定义事件及处理多个事件源的示例代码,帮助读者更好地理解和应用这一强大的编程范式。
246 1
|
11月前
|
消息中间件 druid Kafka
从Apache Flink到Kafka再到Druid的实时数据传输,用于分析/决策
从Apache Flink到Kafka再到Druid的实时数据传输,用于分析/决策
231 0
|
Java API 开发者
【面试题精讲】SPI 和 API 有什么区别?
【面试题精讲】SPI 和 API 有什么区别?
|
算法 安全 开发者
Copilot使用技巧
Copilot使用技巧
366 1
Copilot使用技巧
|
Python
Python中使用`requests`库进行流式响应处理的技术详解
【4月更文挑战第12天】在Python的网络编程中,处理大文件或数据流时,一次性加载整个响应内容到内存中可能会导致内存不足的问题。为了解决这个问题,`requests`库提供了流式响应处理的功能,允许我们逐块读取响应内容,从而更有效地管理内存。本文将详细介绍如何在Python中使用`requests`库进行流式响应处理。
3811 2
|
分布式计算 Java 大数据
java常见的应用场景
java常见的应用场景
806 2
|
前端开发 JavaScript 关系型数据库
深入理解单体架构
深入理解单体架构
496 0