377. 组合总和 IV

题目链接:

本题是爬楼梯的又一变式。
分析样例可知,每次选择的都可以是 \(\rm nums\) 中的任一个数,而最后选择完毕的数之和等于 \(\rm target\).

可以认为我们每次从 \(\rm nums\) 中选一个数作为往上爬的台阶数,问爬 \(\rm target\) 个台阶有多少种方案。

因此爬楼梯那个题可以认为是本题条件下 \(\rm nums = [1,2]\) 的一个特殊情况 ,因为每次只能爬 \(1\) 个或 \(2\) 个台阶。

实现一、记忆化搜索

定义 \(\rm dfs(i)\) 为爬 \(i\) 个台阶的方案数。考虑最后一步爬了 \(\rm x=nums[j]\) 个台阶,那么问题变成爬 \(i-x\) 个台阶的方案数,即 \(\rm dfs(i-x)\)

所以有 \(\rm dfs(i)=\sum\limits_{j=0}^{n-1}dfs(i-nums[j])\)
(仅在 \(\rm nums[j] \leqslant i\) 时成立)

递归边界为 \(dfs(0)=1\),只有当不选取任何元素时,元素之和才为 \(0\),因此只有 \(1\) 种方案。或这样理解:爬 \(0\) 个台阶的方案数为 \(1\)。也可以这样理解:我们从 \(\rm target\) 往下爬,刚好爬到底部(递归边界)此时就找到了一个合法的方案,返回 \(1\)

递归入口为 \(\rm dfs(target)\),也就是答案。

爬楼梯那个题的递推表示也可以由此来推出,即 \(\rm dfs(i)=dfs(i-1)+dfs(i-2)\)

class Solution {
public:
    int combinationSum4(vector<int>& nums, int target) {
        int n = nums.size();
        vector<int> memo(target + 1, -1);

        function<int(int)> dfs = [&] (int i) -> int {
            if (i == 0) return 1;
            int &res = memo[i];
            if (res != -1) return res;
            res = 0;
            for (auto x : nums) {
                if (x <= i) res += dfs(i - x);
            }
            return res;
        };
        return dfs(target);
    }
};

实现二、递推

注意这里需要开到 \(\rm unsigned\) \(\rm long\) \(\rm long\) 才可以。这是因为递推会把所有状态都计算出来,包括无效状态(无法组成的和),但是记忆化搜索找到的一定是可以组成的状态。(无法组成的和 说的是 无法用于组成 target 的某些序列,比如某个序列的和是 target-100, 但没有 序列能组成 100,所以这个序列对组成和为 target 的序列没有贡献)

class Solution {
public:
    int combinationSum4(vector<int>& nums, int target) {
        int n = nums.size();
        vector<unsigned long long> f(target + 1);
        f[0] = 1;
        for (int i = 1; i <= target; i++) {
            for (auto x : nums) {
                if (x <= i) f[i] += f[i - x];
            }
        }
        return f[target];
    }
};
posted @ 2024-04-15 21:54  胖柚の工作室  阅读(16)  评论(0)    收藏  举报