动态规划(dynamic programming)简称DP。

目录

一,重叠子问题、最优子结构

1,斐波那契数列

2,求数列中位数

3,数列的DP问题

4,最短路问题

二,问题本身的固有属性决定数据结构和算法

1,数据结构

2,算法——指针滑动和动态规划

三,空间压缩

四,问题的形式化描述

五,二维DP


一,重叠子问题、最优子结构

先看几个简单的问题:

1,斐波那契数列

1,1,2,3,5,8......

求第n项

int fac(int n)
{if(n<3)return 1;return fac(n-1)+fac(n-2);
}

时间复杂度O (1.6 ^ n)

递归写法(备忘录法):

int ans[1000]={0};
int fac(int n)
{if(n<3)return 1;if(ans[n])return ans[n];return ans[n]=fac(n-2)+fac(n-1);
}

非递归写法:

int ans[1000]={0,1,1};
int fac(int n)
{for(int i=3;i<=n;i++)ans[i]=ans[i-1]+ans[i-2];return ans[n];
}

递归写法不需要控制DP的计算顺序,非递归写法需要严格控制计算顺序。

读者可此处思考一下上述递归写法的实际计算顺序,假设编译器是默认先执行加法左边的,再执行加法右边的。

两种写法的时间复杂度都是O(n)

为什么差异这么大?

这就是DP的第一个重要特性:重叠子问题

2,求数列中位数

这个题,能不能像上一题这么做呢?

如果我用f(n)表示数列前n个数的中位数,那么,由f(n-1)和f(n-2)可以直接推导出f(n)吗?

不能!

差异在哪?差异在于问题本身的性质不同。

这种由子问题的答案可以直接推导出原问题答案的性质,其实就是最优子结构。

最优子结构:问题的最优解包含了子问题的最优解

再来看看一个动态规划的题目,当我们分析它的时候,我们在想些什么?

3,数列的DP问题

一维

力扣 OJ 300. 最长上升子序列

力扣OJ 1218. 最长定差子序列

二维

力扣 OJ 1143. 最长公共子序列

CSU - 1060 Nearest Sequence (三个数组的最长公共子序列)

4,最短路问题

动态规划的题目,当我们分析它的时候,主要就是:

问题研究的对象、问题的子问题、最优子结构(即递推式)、解空间结构

递归写法和非递归写法其实都是解空间的遍历,而绝大部分问题的解空间都可以映射为树空间,树的遍历主要就是DFS和BFS,递归基本上就是DFS。

什么是解空间结构?

解空间就是问题可能的解组成的集合,根据问题的属性,空间的结构也有不同,有的是数组有的是树等等。

按照问题研究的对象类型和解空间结构,DP问题可以分为数列DP、区间DP、树形DP、数位DP、状态压缩DP、概率与期望DP、高维DP等等。

DP的更深用法:

空间压缩、时间优化

二,问题本身的固有属性决定数据结构和算法

1,数据结构

数据结构不仅取决于问题研究的对象本身的数据结构,也取决于在这个对象上,我们需要什么样的形式的一个答案。

例如:力扣OJ 740. 删除与获得点数

同样是数组,我们可以寻求很多种不同形式不同结构的答案。

常规的是一种基于顺序的,或者基于子序列的答案,而本题是基于数值的。

也就是说,我们访问了一个元素之后,要立即删除和他数值隔1的那些数,这个当然不能总靠暴力枚举了,

对于这样的一种形式的问题,我们的数据结构也就需要按照数值来排序,

而实现此目的的方式主要有两种,一种是基于数值的排序,比如快速排序,比如sort函数,一种是基于数值的排序,比如计数排序。

我在力扣OJ 740. 删除与获得点数 中使用的是计数,因为题目规定了每个数不超过10000

2,算法——指针滑动和动态规划

为什么力扣OJ 1248. 统计「优美子数组」是指针滑动,而力扣OJ 1218. 最长定差子序列 不是呢?

依然是由问题的形式决定的。

前者,连续 子数组中恰好有 k 个奇数数字,我们就认为这个子数组是「优美子数组」,这个题目探讨的对象是连续的一段,

后者,等差子序列是一种基于数值的定义,我们不知道能构成等差数列的下一个数在哪,我们只知道这个数是多少。

三,空间压缩

动态规划的空间压缩,本质上就是用时间取代空间的其中一维,使得空间复杂度降低,但时间复杂度不变

具体来说,空间压缩就是,在同一个空间位置,不停刷新值,随着时间的推移,他的含义一直在变化,而他的值一直是对应他的含义。

首先举个最简单的例子感受一下:

求斐波那契数列的第1000项

int ans[1005]={0,1,1};
for(int i=3;i<=1000;i++)ans[i]=ans[i-1]+ans[i-2];

时间复杂度和空间复杂度都是O(n)

而空间压缩的写法:

int a=1,b=1,c;
for(int i=3;i<=1000;i++)c=a+b,a=b,b=c;

这里的a和b就是表示,在我们的计算过程中的斐波那契数列的最后两项的值,随着计算的不断进行,a和b到底是对应哪2个数是一直在变化的。

更常规的例子是二维或者二维以上的DP,使用空间压缩可以降低一个维度。

例如:

力扣 OJ 1223. 掷骰子模拟 力扣OJ(1001-1400)_nameofcsdn的博客-CSDN博客

HDU - 1244 Max Sum Plus Plus Plus 数列DP_nameofcsdn的博客-CSDN博客

CSU 1899: Yuelu Scenes 高维DP_nameofcsdn的博客-CSDN博客

四,问题的形式化描述

动态规划的核心在于分析最优子结构,其中包含如下内容:

如何描述问题,如何描述子问题,问题的解和子问题的解之间的关系,即递推式,问题的解空间结构,如何遍历这个解空间,等等。

问题的描述形式,取决于我们所求的答案的形式和结构.

个人理解主要分三种情形:

(1)完全按照题目所求来描述

(2)基于答案分解,附加特殊规则的精确化描述

(3)自带空间压缩的降维描述

对于问题本身的描述很复杂的题目,我们的描述越精确,我们的递推式也就越简洁。

例如:

(1)完全按照题目所求来描述

例1:力扣 OJ 1143. 最长公共子序列

f(a,b)表示数组A的前a个数和数组B的前b个数的最长公共子序列

(2)基于答案分解,附加特殊规则的精确化描述

什么叫基于答案分解呢?

要求前n个数的最长上升子序列,我们可以把答案分解为n种情况:

以第i个数(i从1到n)为结尾的最长上升子序列

例2:力扣 OJ 300. 最长上升子序列

给定一个无序的整数数组,找到其中最长上升子序列的长度。

f(i)表示以第i个数结尾的最长上升子序列的长度

f(i-1)表示以第i-1个数结尾的最长上升子序列的长度

所以f(i)=max{f(j) | j<i && num[j]<num[i]}

例3:CSU 1225: ACM小组的队列(最长上升子序列的个数)

这个题目需要把以第i个数结尾的最长上升子序列的长度和数目一起算出来。

PS:完全按照题目所求来描述,求得的值就是答案,

基于答案分解,附加特殊规则的精确化描述,求得值之后,还要在所有值里面取一个最优解

PS:基于答案分解是根据答案的最后一个值,分解成很多情况,得到的是分解问题,形式和原问题不同

而子问题是形式和原问题相同,但规模比原问题小,是原问题经过分治思想得到的。

(3)自带空间压缩的降维描述

首先,什么是空间压缩?

ACM总结——动态规划(3)空间压缩 https://blog.csdn.net/nameofcsdn/article/details/106507259

再看看什么叫自带空间压缩的降维描述:

例4:力扣OJ 1218. 最长定差子序列

给定差值dif,求最长等差子序列

如果数组没有重复的数,那就简单,ans[arr[i]]=ans[arr[i]-dif]+1

如果有重复的数,那么相同的数如何选取?

自然是取第i个数之前,等于arr[i]-dif且ans值最大的那个,但是如果搜索的话就会超时,必须在O(1)的时间内找到这个数或者找到它的ans值。

所以我们除了需要知道ans[arr[i]]表示以第i个数结尾的最长等差子序列之外,还需要确定,ans[x]表示啥?

ans[x]=ans[arr[j]],其中j=max{j | arr[j]=x,j<i}

没错,这么一个精准的形式化描述,让我们意识到,ans[x]的含义中包含了i这个变量,也就是说ans数组的值的含义是随着时间一直在变化的,这其实是隐含了空间压缩。

五,二维DP

因为最近在写DP总结系列,所以选了一个二维DP的题目,把各种代码都写了一遍,并在此进一步探索。

力扣OJ 63. 不同路径 II (二维DP)https://blog.csdn.net/nameofcsdn/article/details/113124721

这里面的4个代码,涉及到递归和非递归、非递归的不同顺序、空间压缩。

第一个问题来了,非递归写法有没有空间压缩写法呢?

以题目中的数据和我的递归算法为例

输入:
[
  [0,0,0],
  [0,1,0],
  [0,0,0]
]

int dp(vector<vector<int>>& obs,int x,int y)
{if(x<0||x>=obs.size()||y<0||y>=obs[0].size()||obs[x][y])return 0;if(ans[x][y])return ans[x][y];return ans[x][y]=dp(obs,x,y-1)+dp(obs,x-1,y);
}

首先看看递归写法的调用过程:

可以发现,这棵树其实是一个斜着的矩形,而这个矩形和输入的矩形obs是对应的。

然后,我们看看dp函数实际被调用的过程:

class Solution {
public:vector<vector<int>>ans;int dp(vector<vector<int>>& obs,int x,int y){cout<<x<<" "<<y<<"    ";if(x<0||x>=obs.size()||y<0||y>=obs[0].size()||obs[x][y])return 0;if(ans[x][y])return ans[x][y];return ans[x][y]=dp(obs,x,y-1)+dp(obs,x-1,y);}int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {ans=obstacleGrid;for(int i=0;i<ans.size();i++)for(int j=0;j<ans[0].size();j++)ans[i][j]=0;ans[0][0]=1;return dp(obstacleGrid,obstacleGrid.size()-1,obstacleGrid[0].size()-1);}
};int main()
{vector<int>v1;v1.push_back(0);v1.push_back(0);v1.push_back(0);vector<vector<int>>obs;obs.push_back(v1);obs.push_back(v1);obs.push_back(v1);obs[1][1]=1;Solution s;cout<<s.uniquePathsWithObstacles(obs);return 0;
}

输出:

2 2    2 1    2 0    2 -1    1 0    1 -1    0 0    1 1    1 2    1 1    0 2    0 1    0 0    -1 1    -1 2    2

把-1去掉,得到:

2 2   2 1   2 0   1 0     0 0   1 1   1 2   1 1   0 2   0 1   0 0

用同样的方法,如果输入的是

[
  [0,0,0],
  [0,1,0],
  [0,0,0]
]

那么递归的实际执行过程就是

2 2    2 1    2 0    2 -1    1 0    1 -1    0 0    1 1    1 0    0 1    0 0    -1 1    1 2    1 1    0 2    0 1    -1 2    6

把-1去掉,得到:

2 2    2 1    2 0      1 0      0 0    1 1    1 0    0 1    0 0     1 2    1 1    0 2    0 1

不难看出来,这就是DFS

总结:

1,递归程序都是基于栈的。

2,DFS是基于栈的,BFS是基于队列的。

3,动态规划的问题,解空间都可以映射为树。

4,动态规划的递归程序,是对解空间的DFS。

ACM总结——动态规划相关推荐

  1. ACM题解——动态规划专题——G天上掉馅饼

    ACM题解--动态规划专题--G.天上掉馅饼 题目描述 都说天上不会掉馅饼,但有一天gameboy正走在回家的小径上,忽然天上掉下大把大把的馅饼.说来gameboy的人品实在是太好了,这馅饼别处都不掉 ...

  2. ACM:动态规划,01背包问题

    题目: 有n件物品和一个容量为C的背包.(每种物品均仅仅有一件)第i件物品的体积是v[i],重量是w[i].选一些物品装到这个背包中,使得背包内物品在整体积不超过C的前提下重量尽量大. 解法:两种思路 ...

  3. ubuntu支持中文设置

    http://blog.csdn.net/huixisheng/archive/2010/09/14/5882555.aspx 首页 资讯 研发 移动 云计算 空间 学生 论坛 博客 下载 网摘 程序 ...

  4. 【算法竞赛学习笔记】状压DP

    title : 状压DP date : 2022-3-5 tags : ACM,图论,动态规划 author : Linno 状压DP 状态压缩,是利用二进制数的性质对问题进行优化的一种算法,经常与搜 ...

  5. 网内计算:可编程数据平面和技术特定应用综述

    网内计算:可编程数据平面和技术特定应用综述 摘要--与云计算相比,边缘计算提供了更靠近终端设备的处理,降低了用户体验的延迟.最新的In-Network Computing范例采用可编程网络元素在数据达 ...

  6. 【DP专辑】ACM动态规划总结

    转载请注明出处,谢谢.   http://blog.csdn.net/cc_again?viewmode=list          ----------  Accagain  2014年5月15日 ...

  7. ACM 动态规划(简称dp) 分类

    转载自:   http://blog.csdn.net/cc_again?viewmode=list          ----------  Accagain  2014年5月15日 动态规划博客地 ...

  8. ACM杂题——动态规划_背包问题

    ACM杂题K - I NEED A OFFER!--动态规划_背包问题优化解法 题目描述 Speakless很早就想出国,现在他已经考完了所有需要的考试,准备了所有要准备的材料,于是,便需要去申请学校 ...

  9. ACM学习历程—HDU5586 Sum(动态规划)(BestCoder Round #64 (div.2) 1002)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5586 题目大意就是把一段序列里面的数替换成f(x),然后让总和最大. 首先可以计算出初始的总和,以及每 ...

最新文章

  1. [IIS] [PHP] 500.19 随机出现
  2. 请简述计算机软件系统与硬件系统的关系,电脑硬件与软件的关系是什么?
  3. 安装hadoop1.2.1集群环境
  4. 后台无刷新修改字段js
  5. 雷军:年轻人入职半年内不要提意见,不靠谱;微信表情新彩蛋遭疯狂吐槽:满屏“炸屎”;谷歌正式推出 Fuchsia OS|极客头条...
  6. 关于自学的又一点思考
  7. 王振质问于谦,死得越多说明越伟大?
  8. 社会工程学之《反欺骗的艺术》小结(二)
  9. SWAT模型中土壤水文分组的一些思考
  10. verlay虚拟化技术_Overlay之VXLAN架构
  11. MSP430+CC2500低功耗注意细节
  12. 【ONES 校招前端笔试+一面】
  13. 【转载】英语动词第三人称s的发音规则
  14. 软考__BS家族_WBS_OBS_RBS
  15. Python数据可视化:数据分布图表可视化
  16. Could not resolve all files for configuration “: app: debug Compileclasspath“ mac开发 cordova build踩坑
  17. Golang高效爬虫库colly
  18. Maix Bit K210识别色块(确定目标颜色)【保姆级教程】
  19. 大学计算机相关基本知识,大学计算机基础知识试题
  20. android第三方支付的接入(支付宝,微信,银联,京东,百度等)

热门文章

  1. UE4开发手游过程中,遇到的问题
  2. 【ffmpeg】ffmpeg视频添加水印并解决字体问题Could not load font FreeSerif.ttf:cannot opencv resource
  3. ACM第二周---周赛---题目合集.
  4. wget for windows 下载与安装 (亲测有效)
  5. android 导航栏旋转,旋转建议  |  Android 开源项目  |  Android Open Source Project
  6. css中东布局rtl_如何将RTL支持添加到Flexbox和CSS网格
  7. 概率论笔记:高斯分布的边缘概率
  8. idea创建maven中的servlet并运行
  9. java+springboot+vue驾校网站_毕业设计
  10. 机械工程控制基础复习点