演算法:C++實現動態規劃中的幾個典型問題
阿新 • • 發佈:2019-01-06
動態規劃的思想在程式設計中佔有相當的分量,動態規劃的主要思想就是把大問題劃分為小問題,通過求解小問題來逐漸解決大問題。
滿足動態規劃思想的問題具備兩個典型特徵:
最優子結構:就是說區域性的最優解能夠決定全域性的最優解,最優解的子問題也是最優的。
子問題重疊 :就是說大問題劃分為小問題時,並不是每次都是新問題,有的小問題可能已經在前面的計算中出現過。
下面介紹幾個程式設計人員筆試常遇到的動態規劃問題。
- 連續子陣列的最大和問題
描述:一維模式識別中,常常需要計算連續子向量的最大和,當向量全為正數的時候,問題很好解決。但是,如果向量中包含負數,是否應該包含某個負數,並期望旁邊的正數會彌補它呢?例如:{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;
}