您的位置:首页 > 编程语言 > C语言/C++

一些可以用动态规划(DP)算法解决的问题(C++)

2016-03-15 10:27 549 查看
一、动态规划问题来源:暴力搜索->记忆式搜索->经典的动态规划->改进的动态规划。这也是动态规划问题的求解步骤。本质:利用空间来换取时间。把一个问题分解为相同的子问题,这些子问题的求解是有顺序的,按顺序一步一步求解,前面的步骤和决策使得问题的状态转移到当前状态,当前状态再做出最优的决策,使问题转移到下一个状态,当问题进入最后一个状态时的解就是原问题的解。/article/2874825.html二、练习题1、有数组penny,penny中所有的值都为正数且不重复。每个值代表一种面值的货币,每种面值的货币可以使用任意张,再给定一个整数aim(小于等于1000)代表要找的钱数,求换钱有多少种方法。解法(1):按照经典的动态规划步骤进行,空间复杂度为O(n*aim)
class
Exchange
{
public
:
int
countWays(vector<
int
>
penny,
int
n,
int
aim)
{
if
(penny.empty()||n
==
0
)
return
0
;
vector<vector<
int
>
> dp(n,vector<
int
>(aim+
1
));
for
(
int
i
=
0
;i
< n;i++) {
dp[i][
0
]
=
1
;
}
for
(
int
j
=
1
;j
< aim+
1
;j++)
{
dp[
0
][j]
=j%penny[
0
]
==
0
?
1
:
0
;
}
for
(
int
i
=
1
;i
< n;i++) {
for
(
int
j
=
1
;j
< aim+
1
;j++)
{
dp[i][j]
=(j-penny[i]) >= 
0
?(dp[i-
1
][j]
+ dp[i][j-penny[i]]):dp[i-
1
][j];   
}
}
return
dp[n-
1
][aim];
}
};
解法(2):步骤与经典的动态规划问题一样,但是空间复杂度仅为O(aim)。其实在求dp矩阵时,都是根据上一行的值迭代出当前行的值,所以完全可以只用一维矩阵来存储,不断地更新一维矩阵即可。
class
Exchange
{
public
:
int
countWays(vector<
int
>
penny,
int
n,
int
aim)
{
vector<
int
>
dp(aim +
1
);
for
(
int
i
=
0
;
i <= aim; i++)
if
(i
% penny[
0
]
==
0
)
dp[i]
=
1
;
for
(
int
i
=
1
;
i < n; i++)
for
(
int
j
=
1
;
j <= aim; j++)
if
(
j >= penny[i])
dp[j]
+= dp[j - penny[i]];
return
dp[aim];
}
};
2、有n级台阶,一个人每次上一级或者两级,问有多少种走完n级台阶的方法。解法:f(n)=f(n-1)+f(n-2)。如果直接用递归式求解,中间有很多重复的计算,f(n-1)分支计算过的还得在f(n-2)分支计算一次。然后状态之间的依赖关系是很容易找出了,用动态规划法,一步一步记录相邻两个状态即可,下一个状态等于这两个状态之和。
class
GoUpstairs
{
public
:
int
countWays(
int
n)
{
vector<
int
>
dp(
2
,
0
);
dp[
0
]
=
1
;
dp[
1
]
=
2
;
int
temp;
for
(
int
i
=
3
;i
<= n;i++) {
temp
= dp[
0
];
dp[
0
]
=dp[
1
];
dp[
1
]
=(dp[
1
]+temp)%
1000000007
;
}
return
dp[
1
]%
1000000007
;
}
};
3、有一个矩阵map,它每个格子有一个权值。从左上角的格子开始每次只能向右或者向下走,最后到达右下角的位置,路径上所有的数字累加起来就是路径和,返回所有的路径中最小的路径和。解法:f(n,m)=min(f(n-1,m),f(n,m-1))+map[m]。递归式同样包含很多重复计算,可以根据状态之间的依赖关系一步一步计算出来。走到第一行每个格子的最小路径和很容易求出。根据第一行可以依次求出第二行,依次进行直到计算到最后一行。
class
MinimumPath
{
public
:
int
getMin(vector<vector<
int
>
> map,
int
n,
int
m)
{
vector<
int
>
dp(m,
0
);
dp[
0
]
=map[
0
][
0
];
for
(
int
i
=
1
,j
=
0
;i
< m;i++,j++) {
dp[i]
=map[
0
][i]+dp[j];
}
for
(
int
i
=
1
;i
< n;i++) {
dp[
0
]
+= map[i][
0
];
for
(
int
j
=
1
;j
< m;j++) {
dp[j]
=min(dp[j],dp[j-
1
])+map[i][j];
}
}
return
dp[m-
1
];
}
};
4、(经典的LIS问题)请设计一个尽量优的解法求出序列的最长上升子序列的长度。解法:用dp数组的dp[i]记录下以A[i]结尾的递增子序列中最长的长度,计算dp[i+1]时,遍历A[0~i]找到比A[i+1]小的元素,再比较与这些元素对应的dp数组中的值,找到最大的一个再加1,赋值给dp[i+1]。classLongestIncreasingSubsequence {public:intgetLIS(vector<int> A, intn) {if(A.empty()||n == 0)return0;vector<int> dp(n,0);dp[0] = 1;intresMax = 0;for(inti = 1;i < n;i++) {inttempMax = 0;for(intj = 0;j < i;j++) {if(A[i] > A[j])tempMax = max(tempMax,dp[j]);}dp[i] = ++tempMax;resMax = max(resMax,dp[i]);}returnresMax;}};5、给定两个字符串A和B,返回两个字符串的最长公共子序列的长度。例如,A="1A2C3D4B56",B="B1D23CA45B6A",123456或者12C4B6都是最长公共子序列。解法:经典的动态规划题目(LCS)。利用动态规划表求解。dp[i][j]表示A[0~i]和B[0~j]的最长公共子序列长度。如果A[i]=B[j], 则dp[i][j]一定是dp[i-1][j-1]+1,若A[i]!=B[j],则dp[i][j]要么是dp[i-1][j],要么是dp[i][j-1]。(1)常规解法:对第一行和第一列的处理不够巧妙。
class
LCS
{
public
:
int
findLCS(string
A,
int
n,
string B,
int
m)
{
 
if
(A.empty()||n==
0
||B.empty()||m==
0
)
return
0
;
vector<vector<
int
>
> dp(n,vector<
int
>(m));
for
(
int
i
=
0
;i
< m;i++) {
if
(A[
0
]
==B[i]) {
for
(
int
j
=i;j < m;j++)
dp[
0
][j]
=
1
;
break
;
}
}
for
(
int
i
=
0
;i
< n;i++) {
if
(B[
0
]
==A[i]) {
for
(
int
j
=i;j < n;j++)
dp[j][
0
]
=
1
;
break
;
}
}
for
(
int
i
=
1
;i
< n;i++) {
for
(
int
j
=
1
;j
< m;j++) {
if
(A[i]
==B[j])
dp[i][j]
=dp[i-
1
][j-
1
]+
1
;
else
dp[i][j]
=max(dp[i-
1
][j],dp[i][j-
1
]);
}
}
return
dp[n-
1
][m-
1
];
}
};
(2)最优解:dp矩阵多申请了一行和一列,从而将第一行和第一列的处理融合到后序的处理中。
class
LCS
{
public
:
int
findLCS(string
A,
int
n,
string B,
int
m)
{
vector<vector<
int
>
> dp(n+
1
,vector<
int
>(m+
1
,
0
));
for
(
int
i
=
1
;i<=n
;++i){
for
(
int
j=
1
;
j<=m; ++j){
if
(A[i-
1
]
==B[j-
1
]){
dp[i][j]
=dp[i-
1
][j-
1
]+
1
;
}
else 
{
dp[i][j]
=max( dp[i-
1
][j]
,dp[i][j-
1
]);
}
}
}
return
dp[m];
}
};
6、一个背包有一定的承重cap,有N件物品,每件都有自己的价值,记录在数组v中,也都有自己的重量,记录在数组w中,每件物品只能选择要装入背包还是不装入背包,要求在不超过背包承重的前提下,选出物品的总价值最大。解法:经典的01背包问题。同样利用动态规划表来求解。(1)常规解法:用常规的二维矩阵dp作为动态规划表,第一行第一列单独提前处理。空间复杂度略高。
class
Backpack{
public
:
int
maxValue(vector<
int
>w, vector<
int
>v, 
int
n,
int
cap){
if
(w.empty()||v.empty()||n==
0
||cap==
0
)
return
0
;
vector<vector<
int
>
> dp(n,vector<
int
>(cap+
1
));
for
(
int
j
=
1
;j< cap+
1
;j++)
{
dp[
0
][j]
=w[
0
]<= j?v[
0
]:
0
;
}
for
(
int
i
=
0
;i
< n;i++) {
dp[i][
0
]
=
0
;
}
for
(
int
i
=
1
;i
< n;i++) {
for
(
int
j
=
1
;j< cap+
1
;j++)
{
if
(w[i]> j)
dp[i][j]
=dp[i-
1
][j];
else
dp[i][j]
=max(dp[i-
1
][j],v[i]+dp[i-
1
][j-w[i]]);
}
}
return
dp[n-
1
][cap];
}
};
(2)更优的解法:用一维矩阵dp作为动态规划表。每次用复制构造函数记录上一行的求解结果,根据上一行的求解结果求出当前行的结果后再记录到dp矩阵中。空间复杂度略好。classBackpack {public:intmaxValue(vector<int> w, vector<int> v, intn,intcap) {if(w.empty()||v.empty()||n==0||cap==0)return0;vector<int> dp(cap+1,0);for(inti = 0;i < n;i++) {vector<int> last(dp);for(intj = 1;j < cap+1;j++) {dp[j] = j < w[i]?last[j]:max(last[j],v[i]+last[j-w[i]]);}}returndp[cap];}};推荐博文:二叉树相关练习题(C++)
经典排序算法的C++实现
与字符串有关的一些典型问题的C++解法排列组合相关笔试面试题(C++)与概率相关的算法题C++解法(附证明过程)二分查找的巧妙运用(C++)位运算在算法题中的使用(C++)链表相关练习题(C++)用实例讲解栈和队列(C++)一些智力题的C++解法
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: