动态数组怎么定义_动态规划原来是这样学的

506c8f2455434b54dea6dcbd779638b1.png

Hello,小伙伴们大家好,这篇文章想和大家一起入门一下动态规划算法。

老实说,我感觉动态规划还是很难的,最常见的一种困境就是看答案自己能看明白,但自己想就是想不出来。

81d21a9091dc7f47a5141dcf31f83ecf.png

网上关于动态规划的讲解也有很多,我认为,学会动态规划的关键,就是我们需要形成自己的解题思路,遇见动态规划的题就要知道应该往哪方面去思考。

那怎么才能形成自己的解题思路呢?无非就是通过做题+整理 的方式。动态规划是有一定的套路的,掌握的套路之后,可以说大部分题就可以做出来了。

8cb538508e96d075f45d34969ad9c493.png

这篇文章我先解释一下动态规划的解题步骤,然后重点讲解几个案例,来初步认识一下动态规划算法。

需要先说明的是,我感觉动态规划理解起来有一定难度,尤其是后面两个题,需要有一定的思考量。所以我尽量用通俗的语言解释得详细一些。我也会将题目的链接贴出来,大家可以自己先尝试一下。

文章比较长,我先把文章目录列一下,大家也可以跳着看:

  • 动态规划解题3个步骤
  • 例题一 最大子序和
  • 例题二 打家劫舍问题
  • 例题三 删除与获得节点
  • 例题四 最小路径和
  • 小结

动态规划解题的3个步骤,不同的人有不同的表述,但大体意思是相同的。如下:

  1. 定义dp数组的含义:
    dp数组可能是一维数组,也可能是二维数组,用来保存历史记录。定义dp数组的含义,也就是定义dp[i]表示的是什么。这个步骤不是很好找出,需要通过做题来掌握常见的套路。
  2. 找出数组元素之间的关系式:
    也就是说,dp[i]的值是可能由dp[i-1]、dp[i-2]...来推出来的。这一步骤是最关键的一步,通常也是不容易找出的。
  3. 找出初始值:
    我们知道,只知道递推关系,是不能求出来具体值的,所以我们必须要找出初始值才能求解。

看不懂?没事,下面我通过4道例题来讲解一下,并严格按照上面的四个步骤来做。

例题一 最大子序和

给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。 示例: 输入: [-2,1,-3,4,-1,2,1,-5,4] 输出: 6 解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。

本题是leetcode第53题,难度为 简单

链接

力扣​leetcode-cn.com

解题思路:

  1. 定义dp数组含义:dp[i]表示以nums[i]为结尾的子序和;
  2. 找出数组元素的关系式:我们知道,以dp[i]结尾的子序可以分为两种情况,一种是和nums[i]之前的元素连在一起构成的序列,一种是不和之前的元素连在一起,也就是单独nums[i]一个元素构成的序列。dp[i]应该是在两者之间选择一个最大值。
  dp[i]=Math.max(dp[i-1]+nums[i],nums[i]);

3. 找出初始条件:当i=0时,就没办法再用上面的公式进行计算。即

dp[0]=nums[0].

当数组为空时,返回0;

当数组只含有1个元素时,返回这个元素

最后,返回的结果应该是dp数组中元素的最大值

【代码】(有详细注解)

class Solution {      
 public int maxSubArray(int[] nums) { 
 
 //数组长度为0时,返回0;长度为1时,返回nums[0]    
  if(nums.length==0)      
   return 0;   
   if(nums.length==1)     
   return nums[0]; 
   
   //定义dp数组    
    int []dp=new int [nums.length]; 
    
    //初始值     
    dp[0]=nums[0]; 
    
    //根据递推关系求出dp[i],并返回最大值  
    int max=dp[0];   
   for(int i=1;i<nums.length;i++){      
    dp[i]=Math.max(dp[i-1]+nums[i],nums[i]);     
     if(dp[i]>max)
     max=dp[i];    
       }   
     return max; 
} 
}

案例二 打家劫舍问题本题是leetcode 第198题,难度为 简单

链接

力扣​leetcode-cn.com
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。 示例1: 输入:[1,2,3,1] 输出:4 解释:偷窃1号房屋(金额=1),然后偷窃3号房屋(金额=3),偷窃到的最高金额=1+3=4. 示例2: 输入:[2,7,9,3,1] 输出:12 解释:偷窃 1 号房屋(2) , 偷窃 3 号房屋 ( 9),接着偷窃 5 号房屋 (1)。
偷窃到的最高金额 = 2 + 9 + 1 = 12

解题思路

还是动态规划解题的3个步骤:

  1. 明确dp数组含义:dp[i]表示前i个房屋能偷盗的最高金额(存在第0个房间)

2. 找出递推关系:对于第i个房屋,只有两种选择,选和不选。

若是选第i个房屋,则第i-1个房屋不能选,dp[i]=nums[i]+dp[i-2];

若是不选,则dp[i]=dp[i-1].

dp[i]应是在两者之中选择大的那个。

 dp[i]=Math.max(nums[i]+dp[i-2],dp[i-1]);

3. 明确边界条件

nums.length=0时,返回0;

nums.length=1时,返回nums[0];

nums.length=2时,返回Math.max(nums[0],nums[1]);

最后结果返回dp[m-1].

代码:

class Solution {            
  public int rob(int []nums) {                    
    int m=nums.length; 
    
    //边界条件                     
    if(m==0)                           
      return 0;                
    if(m==1)                        
      return nums[0]; 
    
    //定义dp数组                    
    int dp[]=new int [m];
    
    //根据递推关系求出dp数组元素                   
    dp[0]=nums[0];                
    dp[1]=Math.max(nums[0],nums[1]);         
    for(int i=2;i<m;i++)                   
    {                           
      dp[i]=Math.max(nums[0]+dp[i-2], dp[i-1]);                  
        
    //返回dp[m-1]
    return dp[m-1];                           
    }          
  }
 }

案例三 删除与获得点数

给定一个整数数组 nums ,你可以对它进行一些操作。
每次操作中,选择任意一个 nums[i] ,删除它并获得 nums[i] 的点数。之后,你必须删除每个等于 nums[i] - 1 或 nums[i] + 1 的元素。
开始你拥有 0 个点数。返回你能通过这些操作获得的最大点数。 示例 1: 输入: nums = [3, 4, 2] 输出: 6 解释:删除 4 来获得 4 个点数,因此 3 也被删除。之后,删除 2 来获得 2 个点数。总共获得 6 个点数。 示例 2: 输入: nums = [2, 2, 3, 3, 3, 4] 输出: 9 解释: 删除 3 来获得 3 个点数,接着要删除两个 2 和 4 。之后,再次删除 3 获得 3 个点数,再次删除 3 获得 3 个点数。总共获得 9 个点数。

本题是leetcode第740题,难度为 中等

题目链接:

力扣​leetcode-cn.com

写到这已经有点累了,没办法继续写,大家要认真看啊。

1c60f9bdc5bc47c89bab740981b0bf43.png

解题思路

该问题是打家劫舍问题的一个变形,有一定的难度。难点在于dp数组不是基于原数组来进行定义的,而是基于它的一个变形数组。

构造一个arr数组,以元素的值为下标,以元素的个数为值,例如,nums = [2, 2, 3, 3, 3, 4],则arr=[0,0,2,3,1]。即nums数组中0的个数有0个,1有0个,2有2个,3有3个,4有1个。

每一个nums数组对应唯一的一个arr数组。这样就变成了打家劫舍问题;

1.定义dp数组的含义:dp[i]表示前i个元素能获得的最大点数

2.找出dp数组的递推关系:

对于arr[]数组中第i个元素,只有两种选择,要不选则删除,要不不选,

如果不删除当前位置的数字,那么得到就是前一个数字的位置的最优结果。

如果删除当前的位置数字i,那么就会得到i - 2位置的那个最优结果加上当前位置的数字乘以个数。

以上两个结果,每次取最大的,记录下来,然后答案就是最后那个数字了。

  dp[i]=Math.max(dp[i-1], dp[i-2]+i*arr[i]);

其中arr[i]表示nums数组中i出现的次数

3.找出初始条件:dp[1]=arr[1]*1;

dp[2]=Math.max(dp[1], arr[2]*2);

长度为0时,返回0;

长度为1时,返回该元素;

代码

class Solution {    
public int deleteAndEarn(int[] nums) {  

      //边界条件
     if(nums==null||nums.length==0)       
      {      
       return 0;    
      }  
      if(nums.length==1)  
     {    
       return nums[0];    
     }   
     
   //找出nums数组中的最大值
   int max=nums[0];    
   for(int i=0;i<nums.length;i++)    
   {  
     if(max<nums[i])      
     {      
  max=nums[i];    
     }      
  }  
   
   //构造arr数组
   int []arr=new int [max+1];    
   for(int i=0;i<nums.length;i++)    
   {  
     arr[nums[i]]++;     
   }  
   
   //边界条件
  int []dp=new int [max+1];      
  dp[1]=arr[1]*1;    
  dp[2]=Math.max(dp[1], arr[2]*2);
   
   for(int i=2;i<=max;i++)  
     {    
   dp[i]=Math.max(dp[i-1], dp[i-2]+i*arr[i]);
     }  
     //返回dp数组最后一个元素
     return dp[max];    
  }

案例四 最小路径和

给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。
说明:每次只能向下或者向右移动一步。 示例: 输入:
[
[1,3,1],
[1,5,1],
[4,2,1]
] 输出: 7 解释: 因为路径 1→3→1→1→1 的总和最小。

本题是leetcode第64题,难度为 中等

链接

力扣​leetcode-cn.com

解题思路

同样,应用动态规划解题三步骤,只不过这次dp数组是二维数组:

1. 明确dp数组的含义:dp[i][j]表示从左上角走到(i,j)这个位置时最小的路径和,这样dp[m-1][n-1]就是所要求的。

2. 写出递推关系式:因为每次只能向下走或者向右走,所以有两种方式到达,一种是从(i-1,j)向下走一步,一种是(i,j-1)向右走一步到达,选择其中一种路径较小的,再加上(i,j)位置的值即可

dp[i][j]=Math.min(dp[i-1][j],dp[i][j-1])+grid[i][j];

3. 找出初始值:当i或者j为0时,就不能用上面的公式计算,所以初始值就是dp[0][0...n-1]和dp[0...m-1][0];

也很容易计算得出:

dp[0][j]=dp[0][j-1]+grid[0][j];
dp[i][0]=dp[i-1][0]+grid[i][0];

代码:

class Solution {                
  public int minPathSum(int[][] grid) {       
    int m=grid.length;        
    int n=grid[0].length;       
    int [][]dp=new int [m][n]; 
    
    //边界条件      
    dp[0][0]=grid[0][0];       
    for(int i=1;i<m;i++)       
    {             
      dp[i][0]=dp[i-1][0]+grid[i][0];       
    }       
    for(int j=1;j<n;j++)      
    {            
      dp[0][j]=dp[0][j-1]+grid[0][j];     
    } 
    
    //根据递推关系求出dp[i][j]    
    for(int i=1;i<m;i++)           
      for(int j=1;j<n;j++)         
      {                                       
        
        dp[i][j]=Math.min(dp[i-1][j], dp[i][j-1])+grid[i][j];     
      } 
    //返回最后一个元素  
    return dp[m-1][n-1];    
  }
}

小结

上面这些题,算是动态规划的一些入门题,但对于初学者来说还是有一定难度的。之后会再写一些关于动态规划的题目,还有一些如何优化的,希望对大家有帮助啊。


最后,欢迎关注我的公众号【编程365】,学习更多算法+计算机基础知识

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值