动态规划3-子集和问题

动态规划适用于有以下特征的问题场景:

  • 最优子结构
  • 重叠子问题

DP经典问题

  • 子集和问题

子集和问题

问题描述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[i1](arr[i])dp[i2]+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[i1],dp[i2]+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][75]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[i1][jarr[i]]ordp[i1][j]
递归结束条件

  • 当子集和M=0,dp[i][0]=true
  • 当集合为空,dp[0][i]=false,dp[0][0]除外。
// 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

问题描述3

在问题描述2的基础上,求出当存在和为M时的子集。

e.g.
arr[4] = {1, 3, 4, 5}
返回 {3, 4}

思路

根据递推式的特征,可以得到以下结论:

  • 如果dp[i][j] = dp[i - 1][j] = true,那么arr[i]必定不在子集中。观察dp表可以发现,某一列从上往下,只要前面有一个位置是true,则该列后面的位置都是true。
  • 如果dp[i][j]为true,而dp[i - 1][j]为false, 则dp[i - 1][j - arr[i - 1]]必定为true,且arr[i - 1]必定在子集中。

综上可知,可以从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

你可能感兴趣的:(算法,动态规划,算法,c++)