Logo

子集和问题

给定一个正整数数组 `nums` 和一个目标正整数 `target` ,请找出所有可能的组合,使得组合中的元素和等于 `target` 。给定数组无重复元素,每个元素可以被选取多次。请以列表形式返回这些组合,列表中不应包含重复组合。

没有重复元素的情况

参考全排列解法

回溯算法🐸 全排列问题

出现的问题

  • 由于最后的结果与元素出现的顺序无关,所以需要去重
  • 实际去重时,由于target很大,容易产生大量重复结果,去重开销很大

解决办法:剪枝

💡给定输入数组[x1,x2...xn][x_1,x_2...x_n],搜索过程中选择的序列为[xi1,xi2...][x_{i1},x_{i2}...],应当有[i1i2...][i_1\le i_2 \le ...],不满足该条件的选择序列容易造成重复,应当减去

为实现该剪枝,我们初始化变量 start ,用于指示遍历起始点。当做出选择 xix_i 后,设定下一轮从索引 i 开始遍历。这样做就可以让选择序列满足 i1i2imi_1≤i_2≤⋯≤i_m ,从而保证子集唯一

/* 回溯算法:子集和 I */
void backtrack(vector<int> &state, int target, vector<int> &choices, int start, vector<vector<int>> &res) {
    // 子集和等于 target 时,记录解
    if (target == 0) {
        res.push_back(state);
        return;
    }
    // 遍历所有选择
    // 剪枝二:从 start 开始遍历,避免生成重复子集
    for (int i = start; i < choices.size(); i++) {
        // 剪枝一:若子集和超过 target ,则直接结束循环
        // 这是因为数组已排序,后边元素更大,子集和一定超过 target
        if (target - choices[i] < 0) {
            break;
        }
        // 尝试:做出选择,更新 target, start
        state.push_back(choices[i]);
        // 进行下一轮选择
        backtrack(state, target - choices[i], choices, i, res);
        // 回退:撤销选择,恢复到之前的状态
        state.pop_back();
    }
}

/* 求解子集和 I */
vector<vector<int>> subsetSumI(vector<int> &nums, int target) {
    vector<int> state;              // 状态(子集)
    sort(nums.begin(), nums.end()); // 对 nums 进行排序
    int start = 0;                  // 遍历起始点
    vector<vector<int>> res;        // 结果列表(子集列表)
    backtrack(state, target, nums, start, res);
    return res;
}

有重复元素时

那么本道题中每个元素只能使用一次,只需要在作出选择xix_i后,下一次选择从i+1i+1开始

© 2025 All rights reservedBuilt with Flowershow Cloud

Built with LogoFlowershow Cloud