动态规划的思想在程序设计中占有相当的分量,动态规划的主要思想就是把大问题划分为小问题,通过求解小问题来逐渐解决大问题。
满足动态规划思想的问题具备两个典型特征:
最优子结构:就是说局部的最优解能够决定全局的最优解,最优解的子问题也是最优的。
子问题重叠 :就是说大问题划分为小问题时,并不是每次都是新问题,有的小问题可能已经在前面的计算中出现过。
下面介绍几个编程人员笔试常遇到的动态规划问题。
- 连续子数组的最大和问题
描述:一维模式识别中,常常需要计算连续子向量的最大和,当向量全为正数的时候,问题很好解决。但是,如果向量中包含负数,是否应该包含某个负数,并期望旁边的正数会弥补它呢?例如:{6,-3,-2,7,-15,1,2,2},连续子向量的最大和为8(从第0个开始,到第3个为止)。
int maxSum(std::vector<int> array){
if(array.empty()){
return 0;
}
if(array.size()==1){
return array[0];
}
int sumCurrent=array[0];
int result=array[0];
for(int i=1;i<array.size();++i){
sumCurrent=std::max(sumCurrent+array[i],array[i]);
result=std::max(result,sumCurrent);
}
return result;
}
- 环形公路加油站
描述:有一个环路,中间有N个加油站,加油站里面的油是g1,g2...gn,加油站之间的距离是d1,d2...dn,问其中是否能找到一个加油站,使汽车从这个加油站出发,走完全程。如果存在满足条件的加油站,返回该加油站的序号,否则返回-1。
int select(const std::vector<int> g,const std::vector<int> d){
std::vector<int> ex_g(g.size()<<2);
for(int i=0;i<ex_g.size();++i){ //初始化
ex_g[i]=g[i%ex_g.size()]-d[i%ex_g.size()];
}
//找出n个连续前缀都大于0的序列
int start=ex_g[0];
int oil=ex_g[0];
for(int i=1;i<ex_g.size();++i){
if(oil<0){
start=i;
oil=ex_g[i];
}
else{
oil+=ex_g[i];
if(i-start>=g.size()){
return start;
}
}
}
return -1;
}
- 查找暗黑树
描述:一个只包含’A’、’B’和’C’的字符串,如果存在某一段长度为3的连续子串中恰好’A’、’B’和’C’各有一个,那么这个字符串就是纯净的,否则这个字符串就是暗黑的。例如:BAACAACCBAAA 连续子串”CBA”中包含了’A’,’B’,’C’各一个,所以是纯净的字符串。AABBCCAABB 不存在一个长度为3的连续子串包含’A’,’B’,’C’,所以是暗黑的字符串。你的任务就是计算出长度为n的字符串(只包含’A’、’B’和’C’),有多少个是暗黑的字符串。
#include<iostream>
#include<vector>
long blackNum(int num){
std::vector<long> vec(num+1,0);
vec[1]=3;
vec[2]=9;
for(int i=3;i<=num;i++){
vec[i]=2*vec[i-1]+vec[i-2];
}
return vec[num];
}
int main(){
int num;
std::cin>>num;
std::cout<<blackNum(num)<<std::endl;
return 0;
}
算法说明见动态规划找暗黑树
- 袋鼠过河
描述:一只袋鼠要从河这边跳到河对岸,河很宽,但是河中间打了很多桩子,每隔一米就有一个,每个桩子上都有一个弹簧,袋鼠跳到弹簧上就可以跳的更远。每个弹簧力量不同,用一个数字代表它的力量,如果弹簧力量为5,就代表袋鼠下一跳最多能够跳5米,如果为0,就会陷进去无法继续跳跃。河流一共N米宽,袋鼠初始位置就在第一个弹簧上面,要跳到最后一个弹簧之后就算过河了,给定每个弹簧的力量,求袋鼠最少需要多少跳能够到达对岸。如果无法到达输出-1
输入分两行,第一行是数组长度N (1 ≤ N ≤ 10000),第二行是每一项的值,用空格分隔。
输出最少的跳数,无法到达输出-1
#include<iostream>
#include<vector>
#include<algorithm>
int main(){
int N;
std::cin>>N;
std::vector<int> vec(N,0);
for(int i=0;i<N;++i){
std::cin>>vec[i];
}
int hops=-1;
std::vector<int> dp(N,10000);//辅助vector数组
dp[0]=1;//第一跳
for(int i=0;i<N;i++){//遍历所有情况
for(int j=1;j<=vec[i];j++){//对于vec[i]值,下一跳可以是1-vec[i]的任意值
if(i+j>=N){//符合过桥条件,分两种情况处理
if(hops==-1){
hops=dp[i]==10000?-1:dp[i];
}
else{
hops=std::min(hops,dp[i]);
}
}
//不符合过桥条件,更新跳到当前位置的最小跳数
else{
dp[i+j]=std::min(dp[i+j],dp[i]+1);
}
}
}
std::cout<<hops<<std::endl;
return 0;
}
- 合唱团问题
描述:有 n 个学生站成一排,每个学生有一个能力值,牛牛想从这 n 个学生中按照顺序选取 k 名学生,要求相邻两个学生的位置编号的差不超过 d,使得这 k 个学生的能力值的乘积最大,你能返回最大的乘积吗?
输入:每个输入包含 1 个测试用例。每个测试数据的第一行包含一个整数 n (1<=n<= 50),表示学生的个数,接下来的一行,包含 n 个整数,按顺序表示每个学生的能力值 ai(-50 <= ai <= 50)。接下来的一行包含两个整数,k 和 d (1 <= k <= 10, 1 <= d <= 50)。
输出:输出一行表示最大的乘积。
#include<iostream>
#include<vector>
#include<algorithm>
int main(){
int n;
std::cin>>n;
std::vector<int> students(n);
for(int i=0;i<n;++i){
std::cin>>students[i];
}
int k,d;
std::cin>>k>>d;
//fm[k][i]记录取到K个值且最后一个数的下标为i时,取到的数最大
std::vector<std::vector<long long>> fm(k+1,std::vector<long long>(n,0));
//fm[k][i]记录取到K个值且最后一个数的下标为i时,取到的数最小
std::vector<std::vector<long long>> fn(k+1,std::vector<long long>(n,0));
long long res=0;
for(int i=0;i<n;++i){//遍历所有元素
for(int j=1;j<=k;++j){//保证取到的值不超过k个
if(j==1){
fm[j][i]=students[i];
fn[j][i]=students[i];
continue;
}
//第i值前面的值可能有多种情况,只要间隔不超过d
for(int m=1;m<=d;++m){
if(i-m>=0&&i-m<n){//保证合理区间
//比较取最大
fm[j][i]=std::max(fm[j][i],std::max(fm[j-1][i-m]*students[i],fn[j-1][i-m]*students[i]));
//比较去最小
fn[j][i]=std::min(fn[j][i],std::min(fm[j-1][i-m]*students[i],fn[j-1][i-m]*students[i]));
}
}
}
res=std::max(res,fm[k][i]);//记录每次取到k个数的最大乘积
}
std::cout<<res<<std::endl;
return 0;
}