题解

题目难度:中等难度

知识点:查找、数组、动态规划

解析问题:本题可以分析为典型的01背包问题,使用动态规划就可以解决问题。在分析问题时,主要解析为以下三步。
第一步:确定【状态】和【选择】。
在本题中,【状态】就是“剩余的总和”和“可选择的数”,【选择】就是“放入这个数”和“不放入这个数”。
第二步:要明确 dp 数组的定义。
第三步:根据【选择】,对状态转换的逻辑进行计算。

二维动态规划求解

第二步定义数组意义:在这里,使用二维的思维创建dp的形式。设置 dp[i][j] = true 或者 false ,表示前 i 个数的总和是否等于 j 。
第三步分析逻辑:如果不把 nums[i] 算入子集,那么是否能够恰好等于总和,取决于上一个状态 dp[i-1][j]。如果把 nums[i] 算入子集,那么是否能够恰好等于总和,取决于状态 dp[i - 1][j-nums[i-1]]。

#include <bits/stdc++.h>
using namespace std;
int main()
{
    int N,M;
    cin>>N;
    vector<int> Prices;
    for(int i = 0; i< N; ++i)
    {
        int price;
        cin>>price;
        Prices.push_back(price);
    }
    cin>>M;
    //创建dp的状态
    vector<vector<bool>> dp(N+1,vector<bool>(M+1,false));
    //对边缘数据进行设定
    for (int i = 0; i <= N; ++i)
        dp[i][0] = true;

    for (int i = 1; i <= N; ++i) {
        for (int j = 1; j <= M; ++j) {
            if (j - Prices[i - 1] < 0) {
               // 数值过高,不能购买
                dp[i][j] = dp[i - 1][j]; 
            } else {
               // 购买或不购买
                dp[i][j] = dp[i - 1][j] | dp[i - 1][j-Prices[i-1]];
            }
        }
    }
    cout<< dp[N][M]<<endl;
    return 0;
}

一维动态规划求解

为了更好的节约空间上的复杂度,在观察上述的dp[i][j]的等式时,可以总结出dp[i][j]始终是跟dp[i-1][...]成相关性的,因此可以进行一个维度的降低处理。

#include <bits/stdc++.h>
using namespace std;
int main()
{
    int N,M;
    cin>>N;
    vector<int> Prices;
    for(int i = 0; i< N; ++i)
    {
        int price;
        cin>>price;
        Prices.push_back(price);
    }
    cin>>M;
    //创建dp的状态
    vector<bool> dp(M + 1, false);
    // base case
    dp[0] = true;
    for (int i = 0; i < N; i++) 
        for (int j = M; j >= 0; j--) 
            if (j - Prices[i] >= 0) 
                dp[j] = dp[j] || dp[j - Prices[i]];
    cout<< dp[M]<<endl;
    return 0;
}