算法:C++实现动态规划中的几个典型问题

9 篇文章 0 订阅

动态规划的思想在程序设计中占有相当的分量,动态规划的主要思想就是把大问题划分为小问题,通过求解小问题来逐渐解决大问题。
满足动态规划思想的问题具备两个典型特征:
最优子结构:就是说局部的最优解能够决定全局的最优解,最优解的子问题也是最优的。
子问题重叠 :就是说大问题划分为小问题时,并不是每次都是新问题,有的小问题可能已经在前面的计算中出现过。
下面介绍几个编程人员笔试常遇到的动态规划问题。

  • 连续子数组的最大和问题
描述:一维模式识别中,常常需要计算连续子向量的最大和,当向量全为正数的时候,问题很好解决。但是,如果向量中包含负数,是否应该包含某个负数,并期望旁边的正数会弥补它呢?例如:{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 (1N10000),第二行是每一项的值,用空格分隔。
输出最少的跳数,无法到达输出-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;
}
  • 5
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值