lintcode(17)——子集

博客主要围绕子集和子集 II 问题展开。子集问题要求给定含不同整数集合,返回所有子集;子集 II 问题则针对可能含重复数字的列表。两者都提出用递归与非递归方式解决的挑战,且都有子集元素非降序、解集无重复子集等注意事项。

17. 子集

给定一个含不同整数的集合,返回其所有的子集。

样例 1:

输入:[0]
输出:
[[],[0]]

样例 2:

输入:[1,2,3]
输出:
[[3],[1],[2],[1,2,3],[1,3],[2,3],[1,2],[]]

挑战

你可以同时用递归与非递归的方式解决么?

注意事项

子集中的元素排列必须是非降序的,解集必须不包含重复的子集。

非递归:

class Solution {
public:
    vector<vector<int>> subsets(vector<int> &nums) 
    {
        // write your code here
        vector<vector<int>> result;
        vector<int> temp;
        result.push_back({});
        if(nums.size()==0)
        {
            return result;
        }
        temp.push_back(nums[0]);
        result.push_back(temp);
        for(int i=1;i<nums.size();i++)
        {
            int len=result.size();
            for(int j=0;j<len;j++)
            {
                temp=result[j];
                temp.push_back(nums[i]);
                sort(temp.begin(),temp.end());
                result.push_back(temp);
            } 
        }
        return result;
    }
};

递归: 

class Solution {
private:
    void helper(vector<vector<int> > &result,vector<int> &temp,vector<int> &nums,int index) 
    {
        result.push_back(temp);
        for (int i =index; i < nums.size(); i++) {
            temp.push_back(nums[i]);
            helper(result, temp, nums, i + 1);
            temp.pop_back();
        }
    }
    
 public:
    vector<vector<int> > subsets(vector<int> &nums) {
        vector<vector<int> > result;
        vector<int> temp;

        sort(nums.begin(), nums.end());
        helper(result, temp, nums, 0);

        return result;
    }
    
};

18. 子集 II

给定一个可能具有重复数字的列表,返回其所有可能的子集。

样例 1:

输入:[0]
输出:
[[],[0]]

样例 2:

输入:[1,2,2]
输出:
[[2],[1],[1,2,2],[2,2],[1,2],[]]

挑战

你可以同时用递归与非递归的方式解决么?

注意事项

  • 子集中的每个元素都是非降序的
  • 两个子集间的顺序是无关紧要的
  • 解集中不能包含重复子集

非递归: 

class Solution {
public:
    vector<vector<int>> subsetsWithDup(vector<int> &nums) {
        // write your code here
        vector<vector<int>> result;
        vector<int> temp;
        result.push_back({});
        if(nums.size()==0)
        {
            return result;
        }
        temp.push_back(nums[0]);
        result.push_back(temp);
        for(int i=1;i<nums.size();i++)
        {
            int len=result.size();
            for(int j=0;j<len;j++)
            {
                temp=result[j];
                temp.push_back(nums[i]);
                sort(temp.begin(),temp.end());
                int k;
                for(k=0;k<result.size();k++)
                {
                    if(result[k]==temp)  break;
                }
                if(k==result.size())
                {
                    result.push_back(temp);
                }
            } 
        }
        return result;
    }
};
class Solution {
public:
    vector<vector<int>> subsetsWithDup(vector<int> &nums) {
        sort(nums.begin(), nums.end());
        vector<vector<int>> subsets = {{}};
        vector<int> indexes = {-1}; // 记录subsets中每个集合结尾元素的下标
        for (int i = 0; i < nums.size(); ++i) {
            int size = subsets.size();
            for (int s = 0; s < size; ++s) {
                if (i > 0 && nums[i] == nums[i-1] && indexes[s] != i-1) {
                    continue;  // 去重,如果有重复数字出现,只有前上一个数字选了才能选当前数字
                }
                subsets.push_back(subsets[s]);
                subsets.back().push_back(nums[i]); //在subset的末尾元素中加入nums[i]
                indexes.push_back(i);
            }
        }
        return subsets;
    }
};

 

 

 

 

### 动态规划方法 子集和问题(Subset Sum Problem)是0-1背包问题的一种变种,其目标是找到一组元素的和等于给定值。在本例中,目标是找到若干订单的金额之和等于给定的流水金额 $ V $。 #### 1. 问题描述 给定 $ N $ 个订单金额 $ \text{amounts} = [a_1, a_2, ..., a_N] $ 和目标金额 $ V $,目标是从这些订单中找出若干订单,使得它们的金额之和正好等于 $ V $。如果无法找到这样的组合,也可以考虑找到最接近 $ V $ 的金额。 #### 2. 动态规划解法 动态规划(Dynamic Programming, DP)是一种常用的方法来解决子集和问题。核心思想是构建一个一维数组 `dp`,其中 `dp[v]` 表示是否能够通过选择某些订单的金额,使其和等于 $ v $。 ##### 状态转移方程: - 初始化:`dp[0] = True`(表示金额为 0 时,可以通过不选择任何订单实现)。 - 对于每个订单金额 $ a_i $,从 $ V $ 到 $ a_i $ 反向更新 `dp[v]`: ```python dp[v] = dp[v] or dp[v - a_i] ``` ##### 代码实现: ```python def subset_sum(amounts, V): dp = [False] * (V + 1) dp[0] = True # 初始状态:金额为 0 时,可以通过不选择任何订单实现 for amount in amounts: for v in range(V, amount - 1, -1): if dp[v - amount]: dp[v] = True return dp[V] ``` ##### 示例: 假设 $ \text{amounts} = [100, 200, 300] $,目标金额 $ V = 300 $: - 执行 `subset_sum([100, 200, 300], 300)`,返回 `True`,表示存在子集和为 300 的组合(例如 [100, 200])。 #### 3. 扩展:记录具体订单组合 如果需要记录具体哪些订单构成了目标金额 $ V $,可以通过额外的数组或字典记录路径信息。 ##### 修改后的代码(记录具体订单): ```python def subset_sum_with_path(amounts, V): dp = [False] * (V + 1) path = [[] for _ in range(V + 1)] dp[0] = True for amount in amounts: for v in range(V, amount - 1, -1): if dp[v - amount]: dp[v] = True path[v] = path[v - amount] + [amount] if dp[V]: return True, path[V] else: # 找到最接近 V 的金额 closest_v = max([i for i in range(V + 1) if dp[i]]) return False, path[closest_v] ``` ##### 示例: 假设 $ \text{amounts} = [100, 200, 300] $,目标金额 $ V = 250 $: - 执行 `subset_sum_with_path([100, 200, 300], 250)`,返回 `False, [200, 100]`,表示没有子集和为 250,但最接近的金额是 300,对应的订单是 [200, 100]。 #### 4. 复杂度分析 - **时间复杂度**:$ O(N \times V) $,其中 $ N $ 是订单数量,$ V $ 是目标金额。 - **空间复杂度**:$ O(V) $,用于存储动态规划数组。 #### 5. 优化建议 - 如果目标金额 $ V $ 非常大,可以考虑使用哈希集合代替布尔数组来减少内存占用。 - 对于浮点数金额,可以将金额乘以 100 转换为整数,避免浮点精度问题。 --- ### 相关问题 1. 如何解决浮点数金额的子集和问题? 2. 子集和问题的回溯解法有哪些优缺点? 3. 如何优化动态规划在子集和问题中的空间复杂度? 4. 如果目标金额 $ V $ 非常大,如何调整子集和问题的解决方案? 5. 如何在子集和问题中处理重复订单的情况?
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值