求职期间,做很多公司的笔试题,最后的编程题都是往往都能用动态规划的思路解决,接下来,说一说动态规划问题的个人理解:
首先,解决动态规划问题掌握两点:
1. 动态规划中有三个重要的概念:最优子结构、边界、状态转移公式。
2. 动态规划问题的解决思路:从上往底分析,自底向上求解。
以例题说明:
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
题目一:有一座高度是10级台阶的楼梯,从下往上走,每跨一步只能向上1级或者2级台阶。要求用程序来求出一共有多少种走法。
解:假设只差一步就走到10级台阶,这时会出现两种情况,一是走到第九级,接下来只需一步跨1级;二是走到第八级,接下来一步只需跨2级。如果0到9级台阶有F(9)种走法,0到8级台阶有F(8)种,则0到10级台阶有F(8)+F(9)种走法。以此归纳:
F(1)=1
F(2)=2
F(n)=F(n-1)+F(n-2) (n>=3)
这里F(8)和F(9)是F(10)的最优子结构;F(1)=1和F(2)=2是边界;F(n)=F(n-1)+F(n-2)是阶段和阶段之间的状态转移公式。
我们用到的分析方法是从最顶端开始,解决问题确实从底部开始。这里的原因在于,如果采用从顶部开始计算,其过程类似于一个二叉树,中间有很多重复的计算,复杂度很高。而从底部开始计算,直截了当。
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
接下来,考虑一个更复杂些的问题:
题目二:我们有面值为1元3元5元的硬币若干枚,如何用最少的硬币凑够11元?
解:类似题目一的解决思路,假设只差一枚硬币就凑够11元,则有三种情况,一是已有10元,差一枚1元的;二是已有8元,差一枚3元的;三是已有6元,差一枚5元的;如果凑够10元最少的硬币数是F(10),凑够8元最少的硬币数是F(8),凑够6元最少的硬币数是F(6),则凑够11元的最少硬币数是min{F(10)+1,F(8)+1,F(6)+1}。以此归纳:
F(1)=1
F(2)=2
F(3)=1
F(4)=2
F(5)=1
F(n)=min{F(n-1)+1,F(n-3)+1,F(n-5)+1} (n>=6)
到此,最优子结构、边界、状态转移公式显而易见,问题可解。
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
接下来,考虑一个更更复杂的问题:
题目三:
问题:有一个国家发现了5座金矿,每座金矿的黄金储量不同,需要参与挖掘的工人数也不同(情况如下图)。
金矿编号 | 黄金储量 | 需要人数 |
---|---|---|
1 | 500 | 5 |
2 | 200 | 3 |
3 | 300 | 4 |
4 | 350 | 3 |
5 | 400 | 5 |
参与挖矿工人的总数是10人。每座金矿要么全挖,要么不挖,不能派出一半人挖取一半金矿。要求用程序求解出,要想得到尽可能多的黄金,应该选择挖取哪几座金矿?
解:依然类似之前提到的分析方法。自顶向下分析,假设10人4个金矿的最优选择收益为F[10;4],则10人五个金矿的最优选择收益有两种情况,一是仍为10人4个金矿的最优选择收益,二是,(10-5)人4个金矿的最优选择收益再加上第5个金矿收益。其区别实际上就是是否选择第5个金矿,。以此归纳:
F[10;1]=w1 (p1<=10)
F[10,1]=0 (p1>10)
F[10,n]=max{F[10,n-1],F[10-pn,n-1]+wn} (pn<=10)
F[10,n]=F[n-1] (pn>10)
这里的最优子结构为,F[10,5]=max{F[10,4],F[10-5,4]+400} (5<=10),;边界为F[10;1]=w1 (p1<=10)和F[10,1]=0 (p1>10);状态转移公式为F[10,n]=max{F[10,n-1],F[10-pn,n-1]+wn} (pn<=10) 和 F[10,n]=F[n-1] (pn>10)
其C++代码如下:
- #include<iostream>
- using namespace std;
- int main()
- {
- int n=5,w=10; //金矿数,挖矿工人总数
- int p[]={0,5,3,4,3,5}; //每个矿需要的工人数
- int v[]={0,500,200,300,350,400}; //每个矿的价值
- int Preresult[19];
- int t;
- for(int i=1;i<=10;i++)
- {
- if(i<p[1])
- {
- Preresult[i]=0;
- }
- else
- {
- Preresult[i]=v[1];
- }
- }
- int result[19]={0};
- Preresult[0]=0;
- for(int i=2;i<=n;i++)
- {
- for(int j=1;j<=w;j++)
- {
- //这一处理是避免因访问下标为负的元素而产生异常或者指定的存储单元意外存在数据影响最终结果
- if(j-p[i]<0)
- {
- t=-102876;
- }
- else
- {
- t=Preresult[j-p[i]];
- }
- result[j]=max(Preresult[j],t+v[i]);
- }
- for(int k=1;k<=10;k++)
- {
- Preresult[k]=result[k];
- }
- }
- cout<<result[10]<<endl;
- system("pause");
- return 0;
- }
接下来,考虑一个更更更复杂些的问题:
题目四:给定一个矩阵m,从左上角开始每次只能向右走或者向下走,最后达到右下角的位置,路径中所有数字累加起来就是路径和,返回所有路径的最小路径和,如果给定的m如下,那么路径1,3,1,0,6,1,0就是最小路径和,返回12。
1 3 5 9
8 1 3 4
5 0 6 1
8 8 4 0
解:做类似的分析,走到第(i ,j)个数时,只可能是从(i-1 ,j)或是(i ,j-1)走来的,路径(i ,j)的阶段依赖的是(i-1 ,j)和(i ,j-1)的子阶段,所以状态转移公式为dp[i][j] =a[i][j] + min(dp[i-1][j]+ dp[i][j-1]),其C++代码如下:
- #include <iostream>
- #include <algorithm>
- using namespace std;
- int dp[4][4] = {}; //全局数组,存放决策表
- int main(int argc,char** argv)
- {
- int a[4][4] = {1,3,5,9,8,1,3,4,5,0,6,1,8,8,4,0}; //矩阵存储a[i][j]
- for (int i = 0;i < 4;++i)
- {
- for (int j = 0;j < 4;++j)
- {
- if (i==0 && j==0) //边界条件问题需要考虑到
- {
- dp[i][j] = a[i][j];
- }
- else if (i==0 && j!=0)
- {
- dp[i][j] = a[i][j] + dp[i][j-1];
- }
- else if (i!=0 && j==0)
- {
- dp[i][j] = a[i][j] + dp[i-1][j];
- }
- else
- {
- dp[i][j] = a[i][j] + min(dp[i-1][j],dp[i][j-1]);
- }
- }
- }
- cout<<"走到位置"<<"(4,4)"<<"最短路径为:";
- cout<<dp[3][3]<<endl; //好像到这里又脑残了一次,真输出dp[4][4]了~
- system("pause");
- return 0;
- }