一些可以用动态规划(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++解法
相关文章推荐
- C++指针函数与函数指针
- c++利用socket将url解析成IP(gethostnbyname())
- C++开发工程师面试题库 200~250道
- C++开发工程师面试题库 150~200道
- C++开发工程师面试题库 100~150道
- C++开发工程师面试题库 50~100道
- C++笔试题库之编程、问答题 300~305道
- c++继承经典例子
- C++笔试题库之编程、问答题 200~300道
- C++笔试题库之编程、问答题 150~200道
- C++语言中的static关键字的作用是什么?
- c++中const变量真的不可以修改吗?
- C++开发工程师面试题库 1~50道
- C++笔试题库之编程、问答题 100~150道
- C++全局常量与变量的定义方法
- 深度探索C++对象模型读书读书笔记
- 值得推荐的C/C++框架和库
- C++经典面试题库 附带参考答案
- C语言中结构体指针的定义和引用
- C语言 之 printf () 函数你真的会用吗?