动态规划适用于有以下特征的问题场景:
问题描述1
给定一个正整数集合,从中找到子集和最大的值,要求子集的元素必须是不相邻元素(一旦元素被选中,则其左右的元素都不能被选),返回子集和。
e.g.
arr = {4, 1, 1, 9, 1}
返回 13
思路
设dp[i]为前i个元素所能得到的最大值,则
d p [ i ] = m a x { d p [ i − 1 ] ( 不 选 a r r [ i ] ) d p [ i − 2 ] + a r r [ i ] ( 选 a r r [ i ] ) dp[i]=max\begin{cases} dp[i-1](不选arr[i])\\ dp[i-2]+arr[i](选arr[i])\end{cases} dp[i]=max{dp[i−1](不选arr[i])dp[i−2]+arr[i](选arr[i])
所以递归式为
d p [ i ] = m a x ( d p [ i − 1 ] , d p [ i − 2 ] + a r r [ i ] ) dp[i]=max(dp[i-1], dp[i-2]+arr[i]) dp[i]=max(dp[i−1],dp[i−2]+arr[i])
递归结束条件
dp[0] = arr[0]
dp[1] = max(arr[0], arr[1])
e.g.
arr[7] = {1, 2, 4, 1, 7, 8, 3}
返回 15
arr[5] = {4, 1, 1, 9, 1}
返回 13
// C++实现
int subSetMaxSum(vector &arr)
{
vector dp(arr.size() + 1, 0);
dp[1] = arr[0];
for(int i = 2; i < arr.size() + 1; i++){
dp[i] = max(dp[i-1], dp[i-2]+arr[i - 1]);
}
return dp[arr.size()];
}
问题描述2
给定一个都是正整数的数组序列arr,从中找出一个子序列的和等于M,判断是否存在这样的序列返回bool值或返回子序列(两个问题题型)。
arr[4] = {1, 3, 4, 5} M = 7
返回 true or {3, 4}
思路
对于每个数组元素而言只有选或不选两种结果,比如假设dp[i][j]表示前i个元素的和是否可以等于j(存储bool型的值)。则问题的解就是dp[4][7]。
比 如 : 对 于 元 素 5 − − { 在 子 集 中 ( 选 ) , d p [ 4 ] [ 7 ] = d p [ 3 ] [ 7 − 5 ] 不 在 子 集 ( 不 选 ) , d p [ 4 ] [ 7 ] = d p [ 3 ] [ 7 ] 比如:对于元素5 --\begin{cases} 在子集中(选),dp[4][7] = dp[3][7 - 5]\\ 不在子集(不选),dp[4][7]=dp[3][7]\end{cases} 比如:对于元素5−−{在子集中(选),dp[4][7]=dp[3][7−5]不在子集(不选),dp[4][7]=dp[3][7]
所以易得
d p [ i ] [ j ] = d p [ i − 1 ] [ j − a r r [ i ] ] o r d p [ i − 1 ] [ j ] dp[i][j]=dp[i-1][j - arr[i]]\quad or\quad dp[i-1][j] dp[i][j]=dp[i−1][j−arr[i]]ordp[i−1][j]
递归结束条件
// C++实现
bool subSetSum(vector &arr, int sum)
{
vector > dp(arr.size() + 1, vector(sum + 1, false));
for(int i = 0; i < arr.size() + 1; i++){
dp[i][0] = true;
}
for(int i = 1; i < sum + 1; i++){
dp[0][i] = false;
}
for(int i = 1; i < arr.size() + 1; i++){
for(int j = 1; j < sum + 1; j++){
if(arr[i - 1] > j){
dp[i][j] = dp[i - 1][j];
} else {
dp[i][j] = dp[i - 1][j] || dp[i - 1][j - arr[i - 1]];
}
}
}
/*
for(int i = 0; i < dp.size(); i++){
for(int j = 0; j < dp[0].size(); j++){
cout << dp[i][j] << " ";
}
cout << endl;
}
*/
return dp[arr.size()][sum];
}
//测试
int main(){
int a[4] = {1, 3, 4, 5};
vector arr(a, a + 4);
bool res = subSetSum(arr, 7);
cout << res << endl;
return 0;
}
输出结果为(包含注释输出)
1 0 0 0 0 0 0 0
1 1 0 0 0 0 0 0
1 1 0 1 1 0 0 0
1 1 0 1 1 1 0 1
1 1 0 1 1 1 1 1
1
在问题描述2的基础上,求出当存在和为M时的子集。
e.g.
arr[4] = {1, 3, 4, 5}
返回 {3, 4}
思路
根据递推式的特征,可以得到以下结论:
综上可知,可以从dp矩阵的右下角往左上得到子集。
// C++实现
vector subSetSum(vector &arr, int sum)
{
vector > dp(arr.size() + 1, vector(sum + 1, false));
for(int i = 0; i < arr.size() + 1; i++){
dp[i][0] = true;
}
for(int i = 1; i < sum + 1; i++){
dp[0][i] = false;
}
for(int i = 1; i < arr.size() + 1; i++){
for(int j = 1; j < sum + 1; j++){
if(arr[i - 1] > j){
dp[i][j] = dp[i - 1][j];
} else {
dp[i][j] = dp[i - 1][j] || dp[i - 1][j - arr[i - 1]];
}
}
}
for(int i = 0; i < dp.size(); i++){
for(int j = 0; j < dp[0].size(); j++){
cout << dp[i][j] << " ";
}
cout << endl;
}
// return dp[arr.size()][sum];
vector res;
if(dp[arr.size()][sum]){
cout << "Found a subset with given sum." << endl;
int i = arr.size();
while(i >= 0){
if(dp[i][sum] && !dp[i-1][sum]){
res.push_back(arr[i - 1]);
sum -= arr[i - 1];
}
if(sum == 0){
break;
}
i -= 1;
}
} else{
cout << "No subset with given sum." << endl;
return res;
}
}
测试用例及输出
int main(){
int a[4] = {1, 3, 4, 5};
vector arr(a, a + 4);
vector res = subSetSum(arr, 7);
for(int i = 0; i < res.size(); i++){
cout << res[i] << " ";
}
}
1 0 0 0 0 0 0 0
1 1 0 0 0 0 0 0
1 1 0 1 1 0 0 0
1 1 0 1 1 1 0 1
1 1 0 1 1 1 1 1
Found a subset with given sum.
4 3
优秀题解任意门1
优秀题解任意门2