动态规划及其动态规划经典例题

引言:

动态规划是最重要、最经典的算法之一,学好动态规划对我们十分重要,掌握动态规划对解决某些问题会起到事半功倍的效果。

动态规划:

特点:
①可以把原始问题划分为一系列子问题
②求解每个子问题仅一次,并将其结果保存到一个表中,以后用到时直接存取,不重复计算,节省时间。
③自底向上地计算
适用范围:
原问题可以分为多个相关子问题,子问题的解会被重复使用。

动态规划题目的特点:

1.计数
-有多少种方式走到右下角
-有多少种方法选出k个数的和为sum

2.求最大最小值
-从左上角到右下角路径的最大数字和
-最长上升子序列长度
3.求存在性
-取石子游戏,先手是否必胜
-能不能选出k个数使得和是sum

下面我会用一些力扣上的动态规划经典例题来解释动态规划的用法。请大家将题目和上面的问题特点对号入座。

题目1:零钱兑换
题目链接
给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。

动态规划及其动态规划经典例题_第1张图片
题解:
这道题可以从前往后推,如果目标钱数是0,凑成总金额所需的最少的硬币个数是多少;
目标是1,会怎样呢,…直到目标钱数为amount,需要的最少硬币又是多少呢?
首先要看最后一步,最后一步肯定是dp[i]置为dp[i-coins[j]]+1中最小的那个。所以转换为正推,
如果无法凑够则先把硬币数dp[i]置为最大INT_MAX;否则,则把dp[i]置为最小的dp[i-coins[j]]+1.
然后最后判断一下,如果dp[amount]是否为INT_MAX,如果是,说明无法凑齐,返回-1,如果不是,说明dp[amount]就是最少硬币数。
重要的就是转移方程:dp[i]=min(dp[i],dp[i-coins[j]]+1);

代码:

class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        int dp[amount+1];//因为下面dp[i]=INT_MAX,如果再加1,就会超过范围
        dp[0]=0;
        int i,j;
        for(i=1;i<=amount;i++)
        {
            dp[i]=INT_MAX-10000;
            for(j=0;j<coins.size();j++)
            {
                if(i>=coins[j])
                {
                    dp[i]=min(dp[i],dp[i-coins[j]]+1);
                }
            }
        }
        return dp[amount]==INT_MAX?-1:dp[amount];
    }
};

题目2:不同路径
题目链接
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。
问总共有多少条不同的路径?
动态规划及其动态规划经典例题_第2张图片
动态规划及其动态规划经典例题_第3张图片
题解:
我们首先看最后一步,要想到达(m,n)首先要到达(m-1,n)或者(m,n-1)。设到达(m-1,n)路径有x种,到达(m,n-1)路径有y种,则到达(m,n)的路径有x+y种。
然后再从头看起,到达(1,2)路径只有1种,到达(2,1)路径只有1种,到达(i,j)的路径数是到达(i-1,j)和(i,j-1)的路径和。
重点:转移方程:dp[i][j]=dp[i-1][j]+dp[i][j-1];

代码:

class Solution {
public:
    
    int uniquePaths(int m, int n) {
        int dp[m][n];
        int i,j;
        dp[0][0]=0;
        for(i=0;i<m;i++)
        {
            for(j=0;j<n;j++)
            {
                if(i==0||j==0)
                {
                    dp[i][j]=1;
                }
                else
                {
                    dp[i][j]=dp[i-1][j]+dp[i][j-1];
                }
            }
        }
        return dp[m-1][n-1];
    }
};

题目3:跳跃游戏
题目链接
给定一个非负整数数组,你最初位于数组的第一个位置。数组中的每个元素代表你在该位置可以跳跃的最大长度。判断你是否能够到达最后一个位置。
动态规划及其动态规划经典例题_第4张图片
题解:
我们首先从后面看,要想到达下标为n-1的地方,有两个条件:①能到达下标为 i 的地方;②从 i 能调到 j 。所以我们就得到了转移方程:if(dp[j]&&j+nums[j]>=i) dp[j]=true;
然后我们再从头遍历,判断每个下标为 i 的地方算法能调到,最后返回dp[n-1]即可。

代码:

class Solution {
public:
    bool canJump(vector<int>& nums) {
        int i,j;
        bool dp[nums.size()];
        dp[0]=true;
        for(i=1;i<nums.size();i++)
        {
            dp[i]=false;
            for(j=0;j<i;j++)
            {
                if(dp[j]&&j+nums[j]>=i)
                {
                    dp[i]=true;
                    break;
                }
            }
        }
        return dp[nums.size()-1];
    }
};

题目4:背包问题

因为背包问题比较重要,所以我单独把背包问题拿出来单独写一篇博客。
传送门

题目5:最大子列和
题目链接
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

动态规划及其动态规划经典例题_第5张图片
题解:
如果数组中只有1个数,那么最大子列和就是该数值。如果大于1个,依次遍历数组,如果没考虑当前数的时候,最大子列和小于0,那么考虑到这个数时最大子列和就是该数值;如果没考虑当前数的时候,最大子列和小于0,那么考虑到这个数时最大子列和就是该数值加上之前的最大子列和。然后从这些子列和中选出最大的即可。(可以直接看代码,代码通俗易懂.)

代码:

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
    int dp[nums.size()];
    int i;
    dp[0]=nums[0];
    int maxSum=dp[0];
    for(i=1;i<nums.size();i++)
    {
        dp[i]=max(dp[i-1],0)+nums[i];
        maxSum=maxSum>dp[i]?maxSum:dp[i];
    }
    return maxSum;
    }
};

动态规划总结:

动态规划及其动态规划经典例题_第6张图片

你可能感兴趣的:(算法)