动态规划题目(1)

本文章主要讲解动态规划从入门到比较困难档次的题目,可以让小白实现从零到1的跨越。

这些题目掌握后你就可以做dp入门题如喝水。

动态规划:

动态规划(Dynamic Programming,简称DP)是一种解决复杂问题的算法设计技术,通常用于优化问题,特别是涉及最优化搜索路径、序列或决策的问题。DP 的核心思想是将原问题分解为子问题,然后通过解决子问题来解决原问题,同时避免多次解决相同的子问题,从而提高效率。

这类题型都有固定的步骤:

  1. 定义问题状态:将原问题分解成一系列子问题,通常以状态(state)来表示这些子问题。状态包括了问题的关键特征和参数,它们可以描述问题的不同变化和变化之间的联系。

  2. 找到状态转移方程:确定问题状态之间的转移关系,也就是如何从一个状态转移到另一个状态。这通常是问题的关键部分,因为它定义了如何递归地解决子问题。

  3. 初始化:设置初始状态的值,通常是将基本情况下的状态设定为已知值。

  4. 计算顺序:按照某种规定的顺序计算每个状态的值。通常是自底向上(bottom-up)或自顶向下(top-down)。

  5. 返回结果:根据状态转移方程计算出的值,得到原问题的最优解。

    简单说就是:(最重要)

    1. 确定dp数组(dp table)以及下标的含义:通常就是所求的值取各种i时所对应值的数组
    2. 确定递推公式:通常能由前几个一直推出后边的值,或反过来
    3. dp数组如何初始化:意思是递推公式如何开始运行起来,即判断出递推公式的几个已知值
    4. 确定遍历顺序:从前往后或从后往前,通常就从前往后(**要看,题目中给的是数组前几个数还是后边的数,确定顺序,**从后往前需要递归)
    5. 举例推导dp数组:**for循环运行,求出数组中所有数,**i=n所对应值就为所求值

动态规划常用于解决许多问题,如最短路径问题、背包问题、编辑距离问题、最长公共子序列问题等。它在许多领域中都有广泛的应用,包括计算机科学、经济学、工程学和生物学等。这种方法通常需要合理地定义问题状态和状态转移方程,以便有效地解决问题。

1.典例:斐波那契数列

最能体现动态规划找关系式的案例

详解:

1.确定dp数组以及下标的含义

dp[i]的定义为:第i个数的斐波那契数值是dp[i]

2.确定递推公式dp[i] = dp[i - 1] + dp[i - 2];

3.dp数组如何初始化:

dp[0] = 0;        dp[1] = 1;

4.由递推公式判断为从前往后

5.举例推导dp数组

for循环运行,求i=n时对应的值。

能看出完全符合刚刚讲解的方法的,以后做题一定要想起来!!!

java代码:

class Solution {
    public int fib(int n) {
     if (n <= 1) return n;             
        int[] dp = new int[n + 1];
        dp[0] = 0;
        dp[1] = 1;
        for (int index = 2; index <= n; index++){
            dp[index] = dp[index - 1] + dp[index - 2];
        }
        return dp[n];
    }
}

2.爬楼梯

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。

每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

注意:给定 n 是一个正整数。

示例 1:

  • 输入: 2
  • 输出: 2
  • 解释: 有两种方法可以爬到楼顶。
    • 1 阶 + 1 阶
    • 2 阶

示例 2:

  • 输入: 3

  • 输出: 3

  • 解释: 有三种方法可以爬到楼顶。

    • 1 阶 + 1 阶 + 1 阶
    • 1 阶 + 2 阶
    • 2 阶 + 1 阶
     class 爬楼梯 {//找规律:n=1,有1种,n=2,有两种,n=3,有三种
      //分析当n>=3时,次数应该是n=n-2对应的情况再跨一大步
      // 加上n=n-1对应的情况再跨一小步就是全部的情况
      //注意:n=0时,是有一次的。
        public int climbStairs(int n){
         int []s=new int[n+1];
         s[0]=1;
         s[1]=1;
         if(n>=2)
         for (int i = 2;i 

3.是用最小花费爬楼梯

给你一个整数数组 cost ,其中 cost[i] 是从楼梯第 i 个台阶向上爬需要支付的费用。一旦你支付此费用,即可选择向上爬一个或者两个台阶。

你可以选择从下标为 0 或下标为 1 的台阶开始爬楼梯。

请你计算并返回达到楼梯顶部的最低花费。动态规划题目(1)_第1张图片

java:

 class 是用最小花费爬楼梯746 {
    public int minCostClimbingStairs(int[] cost) {
     
        int []s=new int[cost.length+1];
        //设置到达每层花费最少体力为s[i];
        //又s[i]=s[i-2]跨一大步,或者s[i-1]跨一小步
        //所以s[i]=min(s[i-1]+cost[i-1],s[i-2]+cost[i-2])两者中的较小值
        //这下思路就清晰了,需要知道cost[0],cost[1],就能推出之后的所有值,直到s[cost.length]
       s[0]=0;
       s[1]=0;
        for (int i = 2; i < cost.length+1; i++) {
            s[i]=Math.min(s[i-1]+cost[i-1],s[i-2]+cost[i-2]);
        }
        return s[cost.length];
    }
}

4.不同路径

动态规划题目(1)_第2张图片

难点:得出的数较大但输出的数又需要int,需要进行转换,最后将结果转换成int

public class 不同路径62 {
    public long uniquePaths(int m, int n) {
       //组合

        int s=m+n-2;
        long sum=1;
        int  a=1;
        if(s<=0)
            return 1;
      long   min=Math.min(m-1,n-1);
        for (int i = 0; i 

7.不同路径2

动态规划题目(1)_第3张图片

//有点小难度了,因为没办法考虑绕过障碍物的法子,
// 所以可以先考虑全部,再去除不符合的
public class 不同路径II63 {
    public int uniquePathsWithObstacles(int[][] obstacleGrid) {
//因为障碍物的存在,又给了障碍物的坐标,所以需要把
// 结果中经过障碍物的路径值全都写为0,就结束了,(不止一个障碍物,艹)
        int m=obstacleGrid.length;
        int n=obstacleGrid[0].length;
        int [][]s=new int[m][n];
        if(obstacleGrid[0][0]==1||obstacleGrid[m-1][n-1]==1)
            return 0;
            //因为题中没说条件,所以可能障碍物和终点或起点重合,
        // 从0,0,0开始,因为给的数组是这样,不能自建

        for (int i = 0; i 

最长递增自序列

子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。

示例 1:

输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。
class 最长递增子序列 {
     public int lengthOfLIS(int[] nums) {
//动态规划
         //先给每个数设为1;
         if(nums.length == 0)
             return 0;
         int []dp=new int[nums.length];//存放nums循环i位时最长的递增自序列
         for (int i = 0; i < nums.length ; i++) {
             dp[i]=1;
         }

         //if(nums[i]>nums[i+1])
         //dp[i+1]=dp[i]+1;
         //递推公式:
         for (int i = 0; i nums[j]){
                     dp[i]=Math.max(dp[i],dp[j]+1);
                     //这行意思是求出dp[j]+1的最大值(j在循环,但小于i),仔细看看,对吧?
                     //太恶心人了,呜呜
                     //看图!!
                     //求出数组中每个num[i]结尾的数组所对应的最大长度
                     //记住!!
                 }
             }
         }
         Arrays.sort(dp);
         return dp[dp.length-1];
     }
}

图演示:所以本题意思是,例如:当i=3时,dp[3]最大值只能来自dp[0~2]中

小于nums[3]的nums对应的dp的值加1,如果没有,那么dp[3]=1;比如dp[2]就是这样

当dp[3]时,dp[3]先后判断,1+1=2,2+1=3, 1+1=2,最后3最大,写3

: for (int j = 0; j nums[j]){
dp[i]=Math.max(dp[i],dp[j]+1);}

动态规划题目(1)_第4张图片

最大子数组和

给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

子数组 是数组中的一个连续部分。

示例 1:

输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 
so easy!!!
public class 最大子数组和53 {
    public int maxSubArray(int[] nums) {
//动态规划:很简单,不要想太复杂了,!
        //先建数组,不再赘述
        int[] dp = new int[nums.length];
        for (int i = 0; i < nums.length; i++) {
            dp[i]=nums[i];
        }
        //关系推导式:
        dp[0]=nums[0];
        int max=dp[0];
        for (int i = 1; i < nums.length; i++) {
            int m=0;
                //必须包含nums[i]
//                m=nums[j]+m;
                dp[i] =Math.max(dp[i], dp[i-1]+nums[i]);
            max=Math.max(max,dp[i]);
            }
        return  max;
    }
}

最长公共子序列

给定两个字符串 text1text2,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0

一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。

  • 例如,"ace""abcde" 的子序列,但 "aec" 不是 "abcde" 的子序列。

两个字符串的 公共子序列 是这两个字符串所共同拥有的子序列。

 public int longestCommonSubsequence(String text1, String text2) {
 //        动态规划:数组记录两个字符串中分别i,j长度时对应的最长自序列长度;
        int [][]dp=new int[text1.length()+1][text2.length()+1];
        //推导式,一定要写好推导式和定义的数组含义!!动态规划的关键!
        for (int i = 1; i 

动态规划题目(1)_第5张图片

动态规划题目(1)_第6张图片

整数拆分

给定一个正整数 n ,将其拆分为 k正整数 的和( k >= 2 ),并使这些整数的乘积最大化。

返回 你可以获得的最大乘积

输入: n = 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36。
题解:
public class 整数拆分343 {
    public int integerBreak(int n) {
//动态规划,先定义数组:
        int []dp=new int[n+1];//意思是分拆数字i
        // 得到的最大乘积为dp[i]
        dp[1]=1;
        dp[2]=1;

//dp[3]=1*2=2;即dp[3]=j*(i-j);
        //dp[4]=2*2=4;即j*(i-j)>j*dp[i-j];
        //dp[5]=3*2=6;即j*(i-j)>j*dp[i-j];
        //当i>=5时,dp[i]>i;
        //dp[6]=3*3=9;
        //dp[7]=3*4=12;
//        dp[8]=3*2*3=18;可以发现,当分成3份或以上份数的乘积最大时:
//        dp[i]=j*dp[i-j]>j*(i-j);

//                dp[9]=3*3*3=27;
//                dp[10]=3*3*4=36;
        //得到规律,
        for (int i = 2; i 中值,得到的最大值
            }
        }
        return dp[n];
    }
}

不同的二叉搜索树

给你一个整数 n ,求恰由 n 个节点组成且节点值从 1n 互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。

动态规划题目(1)_第7张图片

public class 不同的二叉搜索树96 {
    public int numTrees(int n) {
 //dp
        int []dp=new int[n+1];
        //每个i对应此时,二叉树的种类
        //假设我们选择根节点的值为 j(1 <= j <= i),
        // 则左子树中包含 j - 1 个节点,右子树中包含 i - j 个节点。
        // 因此,以节点 j 为根的二叉搜索树的数量等于左子树和右子树的数量的乘积。
        dp[0] =1;
        for (int i = 1; i 

你可能感兴趣的:(动态规划,算法)