动态规划相关入门

本文详细介绍了动态规划在解决一系列经典问题中的应用,包括青蛙跳台阶、矩形覆盖、连续子数组最大和、斐波那契数列、股票最大利润、礼物价值计算等。通过实例演示了如何定义状态、编写状态转移方程及设置初始值,以及何时选择使用动态规划方法。
摘要由CSDN通过智能技术生成

目录

一、动态规划

二、题目

1.青蛙跳台阶问题

2.矩形覆盖_牛客网

3.连续子数组最大和_牛客网  https://连续子数组最大和

4.斐波那契数列

5.股票最大利润

6.礼物的最大值力扣


一、动态规划

1.简单将dp问题理解为三个思路:

1)定义状态

2)编写状态转移方程

3)设置初始值

2.什么时候用动归

通过大量题型了解,找到一批动归题目,先练习三种思路

一般使用动归,必定使用数组(一维、二维)

3+1:三个过程+一个数组

二、题目

1.青蛙跳台阶问题

一只青蛙,一次可以跳一级台阶,也可以跳两级,那它到n级台阶有多少跳法1.定义状态

跳台阶_牛客网

1)定义状态

f(n):青蛙跳上n级台阶的总跳法数

f(n-1):青蛙跳上n-1级台阶的总跳法数

f(n-2):青蛙跳上n-2级台阶的总跳法数

2)状态转移方程

f(n)=f(n-1)+f(n-2)

        n级台阶时青蛙一定是从n-1或n-2跳上来的,没有其他跳法,所以它到n的跳法一定是n-1的跳法+n-2的跳法

3)初始值

f(0)=1:还未跳时,可以不要它,如果想要,就认为它是起始台阶,到0台阶有一种方法即不跳

f(1)=1:到第一级台阶就一种跳法

f(2)=2:到第二级台阶两种跳法(先到第一级再到第二级或直接到第二级)

     public int jumpFloor(int target) {
        if(target==0){return 1;}
        if(target==1||target==2){
            return target;//加上否则有越界访问问题,问题输入是输入1越界
        }
//        //f(n)=f(n-1)+f(n-2)
//        int[] dp=new int[target+1];//下标+1,让第一个台阶从1开始,第二个台阶从2开始
//        dp[0]=1;
//        dp[1]=1;
//        dp[2]=2;
//        for(int i=2;i<=target;i++){
//            dp[i]=dp[i-1]+dp[i-2];
//        }
//        return dp[target];
        int first=1;
        int second=1;
        int third=1;
        for (int i = 2; i <=target ; i++) {
            third=first+second;
            first=second;
            second=third;
        }
        return third;
    }

2.矩形覆盖_牛客网

        我们可以用 2*1 的小矩形横着或者竖着去覆盖更大的矩形。请问用 n 个 2*1 的小矩形无重叠地覆盖一个 2*n 的大矩形,从同一个方向看总共有多少种不同的方法?

分析:

1)状态定义:f(n):用n个2*1的小矩形无重叠的覆盖一个2*n的大矩形所用的总方法数

2)状态递推:f(n)=f(n-1)【最后一个竖着放】+f(n-2)【最后两个横着放】

f(n-1):最后一个是竖着放时前面总共的放法数;f(n-2):最后两个是横着放时前面的总放法数

3)初始化:f(1)=1;f(2)=2;

 public int rectCover(int target) {
        //f(n):2*1->2*n:总方法数
        if(target==0||target==1||target==2){
            return target;
        }
        int[] dp=new int[target+1];//最后一个元素的下标是target,target是5的话就是第五次
        dp[1]=1;
        dp[2]=2;
        for(int i=3;i<=target;i++){
            dp[i]=dp[i-1]+dp[i-2];
        }
        int res=dp[target];
        return res;
    }

3.连续子数组最大和_牛客网  https://连续子数组最大和

分析:用到动态规划,状态方程式为:

max(dp[i])=getMax(max(dp[i-1]+arr[i],arr[i]),dp[i-1])//max(dp[i-1]+arr[i],arr[i])是当前dp[i]的值

2-3-27-5122

      dp[i]:以i结尾的数组元素的最大和,如dp[3]时,有两种求法:1)dp[i-1]+arr[i];2)arr[i],求法1得到结果2-3-2+7=4,求法2得到结果就是7,求法二得到结果较大,所以取较大值,dp[3]=7。

      取到当前max(dp[i])后再循环向后遍历,拿到每个对应位置的max(dp[i]),同时将相对大的值赋值给结果result,最后输出的result就是连续子集的最大和了。

import java.util.*;
public class Num2{
    public static int getMax(int a,int b){
        return a>b?a:b;
    }
    public static void main(String[] args){
        Scanner sc=new Scanner(System.in);
        int n=sc.nextInt();//数组元素个数
        int[] array=new int[n];
        for(int i=0;i<n;i++){
            //给数组元素赋值
            array[i]=sc.nextInt();
        }
        //将0位置索引值作为初始值,i从1开始
        //当前子数组的和
        int sum=array[0];
        //连续子数组最大和——最终要输出的结果
        int max=array[0];
        for(int i=1;i<n;i++){
            //获取arr[i]+sum(以i-1结尾的数组的最大和)与arr[i]的较大值
            //初始以i-1结尾的数组的最大和就是array[0]
            sum=getMax(sum+array[i],array[i]);
            //判断此时的sum和max哪一个大(dp[i]和dp[i-1]比较)
            if(sum>=max){
                max=sum;
            }
        }
        System.out.println(max);
    }
}

一样的题型:给定整数序列求连续子串最大和_牛客网 https://www.nowcoder.com/questionTerminal/9d216205fbb44e538f725d9637239523

import java.util.*;
public class Main{
    public static int getMax(int a,int b){
        return a>b?a:b;
    }
    public static void main(String[] args){
        Scanner sc=new Scanner(System.in);
        String[] s=sc.nextLine().split(" ");
        int[] array=new int[s.length];
        for(int i=0;i<array.length;i++){
            array[i]=Integer.parseInt(s[i]);
        }
        //此时拿到整型数组
        //开始进行最大子数组求和
        int sum=array[0];//sum计算直到当前i位置的所有元素的最大子数组元素和
        int max=array[0];//max用来记录每次最大的sum和max的较大值,max记录最终结果
        for(int i=1;i<array.length;i++){
            //直到当前i位置最大子数组元素和
            sum=getMax(sum+array[i],array[i]);
            //比较max与sum大小
            if(sum>=max){
                //更新结果集
                max=sum;
            }
        }
        System.out.println(max);
    }
}

4.斐波那契数列

斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。

答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1

int MOD=(int)le9+7,进行取模操作即可。

/**
 * 求n的斐波那契数列
 **/
public class Num10I_斐波那契数列 {
    public int fib(int n) {
        final int MOD = 1000000007;//对大数进行取模操作,让结果不至于溢出
        if(n==0||n==1){
            return n;
        }
        if(n==2){
            return 1;
        }
        int first=1;
        int second=1;
        int third=1;
        while (n>2){
            third=first+second;
            third%=MOD;
            first=second;
            second=third;
            n--;
        }
        return third;
    }
}

5.股票最大利润

1)

/**
 * 假设把某股票的价格按照时间先后顺序存储在数组中,请问买卖该股票一次可能获得的最大利润是多少?
 * 输入: [7,1,5,3,6,4]
 * 输出: 5
 * 解释: 在第2天(股票价格 = 1)的时候买入,在第5天(股票价格=6)的时候卖出,最大利润 = 6-1 = 5 。
 *      注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格
 **/

/**
 * max(price[j]-price[i]):最大差值。
 * 我们找出数组中两数字之间最大差值即可获得最大利润,并且第二个数>第一个数
 * 我们只要用一个变量记录一个历史最低价格 minprice,我们就可以假设自己的股票是在那天买的。
 * 那么我们在第 i 天卖出股票能得到的利润就是 prices[i] - minprice
 */
public class Num63_股票最大利润 {
    public int maxProfit(int[] prices) {
        int minPrice=Integer.MAX_VALUE;//初始化为整型最大值
        int maxProfit=0;//最大利润(利润=当前遍历价格-最低价格,最大利润取最大值即可)
        for (int i = 0; i < prices.length; i++) {
            if(prices[i]<minPrice){//将最小值放入minPrice中
                minPrice=prices[i];
            }
            if(prices[i]-minPrice>maxProfit){//当前遍历价格-最低价格>差价,更新差价
                maxProfit=prices[i]-minPrice;
            }
        }
        return maxProfit;
    }
}

2)动态规划求解

定义状态:dp[i]:以prices[i]为结尾的子数组的最大利润(即前i日的最大利润)

转移方程:前i日最大利润=前i-1日最大利润dp[i-1]和第i日最大利润中的较大值

                                        =max(前(i−1)日最大利润,第i日价格−前i日最低价格)
                  dp[i]=max(dp[i−1],prices[i]−min(prices[0:i]))

初始状态:dp[0]=0

 /**
     * 定义状态:dp[i]:以prices[i]为结尾的子数组的最大利润(即前i日的最大利润)
     * 转移方程:前i日最大利润=前i-1日最大利润dp[i-1]和第i日最大利润中的较大值
     * dp[i]=max(dp[i−1],prices[i]−min(prices[0:i]))
     */
    public int maxProfit(int[] prices) {
        int minPrice=Integer.MAX_VALUE;//购入的最小价格
        int profit=0;//利润
        for (int price : prices) {//price的值直接是数组中元素的值
            minPrice=Math.min(minPrice,price);//最小购入价格
            profit=Math.max(profit,price-minPrice);//右profit是上一循环中的差价,price-minPrice是当前购入值与最小值的差价,比较大者给profit
        }
        return profit;
    }

6.礼物的最大值力扣

1.定义状态:设动态规划矩阵 dp(i,j)代表从棋盘的左上角开始,到达单元格(i,j) 时能拿到礼物的最大累计价值。

2.状态方程:grid(i,j)=max[f(i,j−1),f(i−1,j)]+grid(i,j)

/**
 * 某单元格只可能从上边单元格或左边单元格到达。
 * 设f(i, j)为从棋盘左上角走至单元格(i ,j)的礼物最大累计价值
 * 易得到以下递推关系:f(i,j)f(i,j) 等于 f(i,j-1)f(i,j−1) 和 f(i-1,j)f(i−1,j) 中的较大值加上当前单元格礼物价值 grid(i,j)grid(i,j) 。
 * f(i,j)=max[f(i,j−1),f(i−1,j)]+grid(i,j)
 **/

/**
 * 状态方程
 * 当 i=0 且 j=0 时,为起始元素;最大价值就是当前单元格礼物价值;dp(i,j)=grid(i,j)
 * 当 i=0 且 j!=0时,为矩阵第一行元素,只可从左边到达;dp(i,j)=grid(i,j)+dp(i-1,j);
 * 当 i!=0且 j=0 时,为矩阵第一列元素,只可从上边到达;dp(i,j)=grid(i,j)+dp(i,j-1);
 * 当 i !=0 且 j!=0 时,可从左边或上边到达dp(i,j)=grid(i,j)+max(dp(i,j-1),dp(i-1,j));
 */
public class Num47_礼物的最大价值 {
    public int maxValue(int[][] grid) {
        int m=grid.length;//行
        int n= grid[0].length;//遍历列
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if(i==0&&j==0){
                    //其实元素,最大价值就是当前格元素的礼物价值
                    continue;
                } else if(i==0){
                    //一行
                    grid[i][j]=grid[i][j]+grid[i][j-1];
                } else if(j==0){
                    //一列
                    grid[i][j]=grid[i][j]+grid[i-1][j];
                } else {
                    grid[i][j]=grid[i][j]+Math.max(grid[i-1][j],grid[i][j-1]);
                }

            }
        }
        return grid[m-1][n-1];
    }
}

7.力扣_数字打印成字符串;

1.如果是最后一个字符:整个数字的翻译结果=除去最后一位的部分的翻译结果(根据题目,0-9都可以翻译,所以最后一位一定可以翻译并且只有一种法子翻译)

2.如果恰好最后两个字符可以翻译,整个=除去最后2位部分的翻译结果,因为最后两位如果可译,这两位也只会对应一种翻译结果:

3.总结一二中的两种情况考虑,整个数字的翻译结果f(i)=f(i-1)位上的总翻译结果+f(i-2)上的总翻译结果:

4.dp

定义状态:dp[i]表示nums[0...i]前i位能翻译成字符串的种类数

状态转移方程:dp[i]=dp[i-1]+dp[i-2](若nums[i-1...i]可以翻译)

初始状态:dp[0]=1(0-9各自可以翻译成一种结果)

输出:dp[len-1]


/**
 * 一串数字翻译成字符串多少种翻译方法
 **/
public class Num46_把数字翻译成字符串 {
    //dp[i]=dp[i-1]:因为一个数字一定可以翻译,所以dp[i]的值至少是dp[i-1]
    //dp[i-2]不能组合时,dp[i]=dp[i-1],可以组合时,dp[i]=dp[i-1]+dp[i-2]
    public int translateNum(int num) {
        //数字转化为字符串,字符串转化为数组
        String s=String.valueOf(num);
        int length=s.length();
        if(length<2){
            //只有一位数或没有数字,返回1或0种方法
            return length;
        }
        char[] charArray=s.toCharArray();
        int[] dp=new int[length];
        dp[0]=1;
        for (int i = 1; i < length; i++) {
            dp[i]=dp[i-1];
            //判断当前位和前一位能否组成10-25的数(dp[i-2]是否存在)
            int n=10*(charArray[i-1]-'0')+(charArray[i]-'0');
            if(n>9&&n<26){
                if(i<2){
                    dp[i]++;
                }else{
                    dp[i]+=dp[i-2];
                }
            }
        }
        //循环完毕,dp数组最后一位即为最终统计值
        return dp[length-1];
    }
}

8.力扣_整数拆分

/**
 * 给定一个正整数 n ,将其拆分为 k 个 正整数 的和( k >= 2 ),并使这些整数的乘积最大化。
 * 返回 你可以获得的最大乘积 。
 **/

/**
 * dp[i] 表示将正整数i拆分成至少两个正整数的和之后,这些正整数的最大乘积
 * 特别地,0不是正整数,1是最小的正整数,0和1都不能拆分,因此dp[0]=dp[1]=0
 * 当i≥2时,假设对正整数i拆分出的第一个正整数是j(1≤j<i),则有以下两种方案:
 * 将i拆分成j和i−j的和,且i−j不再拆分成多个正整数,此时的乘积是j×(i−j);
 * 将i拆分成j和i−j的和,且i−j继续拆分成多个正整数,此时的乘积是j×dp[i−j]
 */
public class Num343_整数拆分 {
    public int integerBreak(int n) {
        int[] dp=new int[n+1];
        for (int i = 2; i <= n; i++) {
            int resMax=0;
            for (int j = 0; j < i; j++) {
                resMax=Math.max(resMax,Math.max(j*(i-j),j*dp[i-j]));
            }
            dp[i]=resMax;
        }
        return dp[n];
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值