动态规划算法

  • Post author:
  • Post category:其他


一、前言

动态规划是一种常用的算法,在算法领域十分重要,但对于新手来说,理解起来有一定的挑战性,这篇博客将明确步骤来一步一步讲解动态规划到底该如何理解与运用。

二、解析动态规划算法

1.特点


①把原来的问题分解成了【要点相同】的子问题(这个要点可以和后面讲的状态联系起来理解)


②所有问题都只需要解决一次(解决一次就是只需要由后面所说的状态转移方程解决即可)




前这两个特点可以看出,其实动态规划就是以对解答问题的每个步骤具化成状态,用通过状态与状态之间的递推关系写出的状态转移方程来解决实际问题的算法



③储存子问题的解(状态转移方程中往大问题转移一定需要子问题的解,所有需要将子问题的解储存起来)

2.要素


①状态的定义


②状态间的转移方程定义


③状态的初始化


(初始状态确定已知)


④返回结果


(返回的结果恰好完全符合题目要求)

3.用动态规划解题步骤


①根据题目所给条件,以及所问问题,初步判断是否具有动态规划的特点(可分解,可一次解决,可储存)


②对状态做出定义后判断当前状态可以由哪几种子状态通过哪几种方式(紧扣题目条件)得到,并以此为依据写出状态转移方程,并找全四个要素


③写代码

4.例题




题目



跳台阶扩展问题




一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶(n为正整数)总共有多少种跳法


分析


所以要想跳到第n个台阶,我们可以从第1个台阶跳上来,也可以从第2个台阶跳上来……,很明显有小问题推大问题的情节



递推公式是




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




同样如果我们想跳到第n-1个台阶,也可以列出下面这个公式




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




通过这两个公式我们可以得出




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




实际上他是个等比数列,base case当n等于1的时候结果为1,来看下代码


代码

class Solution {
public:
    int jumpFloorII(int number) 
    {
        if(number==0||number==1)
        {
            return number;
        }
        else
        {
            int ret=1;
            while(number>1)
            {
                ret=ret*2;
                number--;
            }
            return ret;
        }
    }
};




题目



最大连续子数组和




输入一个整形数组(可能有正数和负数),求数组中连续子数组(最少有一个元素)的最大和。要求时间复杂度为O(n)


分析


先假设以索引i-1结束时为状态,那么这个状态下时最大和怎么推导索引i结束时最大和呢,逻辑思考后可以发现,真实的最大连续子数组和肯定以某个索引结束的,所以找到dp中最大值即可。填充dp时,如果dp[i-1] < 0,那么dp[i]直接等于第i个数nums[i],否则就是dp[i] = dp[i-1] + nums[i]。


代码

 int main () {
    int n = 0, temp = 0;
    cin >> n;
    vector<int>  nums, dp(n, 0);
    while (cin >> temp) nums.push_back(temp);
    dp[0] = nums[0];
    for (int i = 1; i < n; i++) {
        if (dp[i-1] < 0) dp[i] = nums[i];
        else dp[i] = dp[i-1] + nums[i];
    }
    cout << *max_element(dp.begin(), dp.end()) << endl;
    return 0;
}




题目



单词拆分




给定一个字符串和一个字符串数组,在字符串的任意位置拆分任意次后得到的字符串集合是否是给定字符串数组的子集。


分析

出发点:给定字符串s的每个字符i

最优解:对于每个i之前的字符序列,分为j(0<=j<=i)前面的序列和j后面的序列,求解i转化为求解j和i-j

状态d(i):i之前的字符序列分段后的字符串能不能在字典中找到

递推公式:d(i) = d(i)||(d(j)&sub(i-j)), 其中j=0~i


代码

#include
using namespace std;
class Solution {
public:
    bool wordBreak(string s, unordered_set &dict) {
        // 动态规划:1\. 每一个状态和前一个状态有关;2\. 最小子状态是可以求得的
        // 转移方程:f(i)表示s[0,i]可以分词,那么f(i) = f(j) && f(j+1, i); (0<=j<i)
        int len = s.length();
        vector dp(len+1, false);
        dp[0] = true;
        for (int i = 1; i <= len; ++i) {        // 注意终止条件,此处从1开始,终止条件应该为len
            for (int j = i-1; j >= 0; --j) {
                if (dp[j] && dict.find(s.substr(j, i-j)) != dict.end()) {
                    dp[i] = true;
                    break;
                }
            }
        }
        return dp[len];
    }
};


~感谢观看❥(^_-)



版权声明:本文为phantomthief1412原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。