动态规划(九章算法)详细学习

1.什么是动态规划

存在最优子程序,跟分治最大区别就是存在很多相同的子问题,如果用递归就会存在相同的值算了很多次。所以可以用数组的形式将已经计算出来并且后面又需要用得到的保存在数组中。
一般求最值(最大/最小)、求总值、存在性问题

2.基本步骤

以最少零钱兑换题目为例:注意,存在无法找零钱(2、5、7)的情况,此时返回-1即可。但是在写程序的时候就要注意了,可将无法找够零钱的值设置为无穷大。
(1)确定状态,一维还是二维,首先由最后一步分析状态:最后一枚硬币可以是2、5、7,所以最少硬币会在这三种情况里面出现。dp[i]表示要找i元零钱时所找的最少的硬币数。
(2)确定子问题,状态转移函数:dp[i]=min(dp[i-coins[j]]+1),j取coins的大小-1,在这里需要具体问题具体分析,但是整体思路不变。
(3)确定初始值,dp[0]=0,表示要找0元钱的时候硬币最少为0。
(4)确定计算顺序,一般是根据状态转移函数中的右边的值需要先计算得出。

最后还有一些细节方面的问题,需要根据题目来进行完善!

public static int minCountCoins(int money,int []coins){
        //只有2/5/7三种价值的硬币,如果无法找零钱,那就返回-1。
        //在这里无法找零钱的都设置为无穷大=Integer.MAX_VALUE;
        int dp[]=new int[money+1];
        //找零为0元时,硬币数为0,设置初值
        dp[0]=0;
        //状态转移方程为:dp[i]=min(dp[i-1]+1,dp[i-2]+1,dp[i-5]+1);
        for(int i=1;i<=money;i++){
            dp[i]=Integer.MAX_VALUE;
            for(int j=0;j<coins.length;j++){
                if(i>=coins[j]&&dp[i-coins[j]]<Integer.MAX_VALUE)
                    dp[i]=Math.min(dp[i],dp[i-coins[j]]+1);
            }
        }
        if(dp[money]==Integer.MAX_VALUE){
            return -1;
        }
        else{
            return dp[money];
        }
    }

3.题型:

(1)求总路径数目问题:

路径总和求解:
1、一个M*N的矩阵,即可看作是棋盘,然后求从左上角到右下角(只能向下、向右走)全部路径数目。
2、二维的:所以状态dp[i][j]表示从左上角(0,0)到(i,j)位置的所有的路径数目。
3、最优子问题:dp[i][j]=dp[i-1][j]+dp[i][j-1]
//初始值:dp[0][j]=dp[i][0]=1,因为只有一条路径。
//计算顺序,由上到下、由左向右。

    public static int SumPath(int M,int N){
        int [][]dp=new int[M][N];
        for(int i=0;i<M;i++){
            dp[i][0]=1;
        }
        for(int j=0;j<N;j++){
            dp[0][j]=1;
        }
        for(int i=1;i<M;i++){
            for(int j=1;j<N;j++){
                dp[i][j]=dp[i-1][j]+dp[i][j-1];
            }
        }
        return dp[M-1][N-1];
    }

(2)判断存在性问题:青蛙跳砖问题

1、青蛙跳转问题,青蛙从0号砖出发,每次最多可跳cost[i]块砖,求能不能到达第N块砖。
2、状态:dp[i]表示能不能到到达第i块砖。
3、子问题:能不能到达第j块砖即dp[j]=true,且cost[j]>=i-j。
4、状态转移这里就涉及到了两次循环,每次求的时候j都要从最近的开始进行遍历,找到就退出。
5、求解方向:从小到大。

public static boolean judgeExist(int N,int[] cost){
        boolean dp[]=new boolean[N];
        dp[0]=true;
        for(int i=1;i<N;i++){
            dp[i]=false;
            for(int j=i-1;j>=0;j--){
                if(dp[j]&&(cost[j]>=(i-j))) {
                    dp[i] = true;
                    break;
                }
            }
        }
        return dp[N-1];
    }

(3)作业:乘积最大子数组

给你一个整数数组 nums ,请你找出数组中乘积最大的连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。

示例 1:
输入: [2,3,-2,4]
输出: 6
解释: 子数组 [2,3] 有最大乘积 6。

示例 2:
输入: [-2,0,-1]
输出: 0
解释: 结果不能为 2, 因为 [-2,-1] 不是子数组。

在这里其实是动态规划,但是需要非常注意,最开始想的状态也没毛病,dp[i]表示最后包含nums[i]的子数组的最大乘积,但是有一点有问题,那就是真的能从dp[i-1]直接推出dp[i]吗?因为存在负数,所以会存在翻转现象,即本来这样是负的,所以会被抛弃,但是由于下一个是负的会导致对于下一个来说这样的乘积更大

举个例子:[-2,3,-4]
dpmax[0]=-2;
dpmax[1]=3;
dpmax[2]=-4!!!,是错的!!!,dpmax[2]=24;
所以考虑到翻转的问题,我们需要同时维护dpmin这个数组去记录最小值,以便于考虑其翻转的情形。
class Solution {
    public int maxProduct(int[] nums) {
        int[] dpmax=new int[nums.length];
        int[] dpmin=new int[nums.length];
        dpmax[0]=nums[0];
        dpmin[0]=nums[0];
        int max=nums[0];
        for(int i=1;i<nums.length;i++){
            dpmax[i]=Math.max(Math.max(dpmin[i-1]*nums[i],dpmax[i-1]*nums[i]),nums[i]);
            dpmin[i]=Math.min(Math.min(dpmin[i-1]*nums[i],dpmax[i-1]*nums[i]),nums[i]);
            max=Math.max(max,dpmax[i]);
        }
        return max;
    }
}

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