LeetCode416.分割相同子集

该博客详细介绍了LeetCode416题目的解决方案,包括回溯法和动态规划两种策略。首先,通过回溯法暴力搜索所有可能的子集,然后优化为动态规划,利用01背包的思想降低时间复杂度。博客还探讨了如何通过滚动数组进一步优化空间复杂度,最后展示了动态规划的代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

LeetCode416.分割等和子集

相似题目

在这里插入图片描述

难易程度

  • middle

题目考察

  • 回溯
  • 动态规划

暴力解法

思路及算法:最容易想到的方法是用回溯法暴力搜索所有答案,使用二维数组res保存结果,path保存找到的一个数组。

//时间复杂度:O(N * 2^ N) 空间复杂度:O(N * M)
#include<iostream>
#include<vector>
#include<algorithm>

using namespace std;

class Solution{
public:
	vector<vector<int>> res;
	vector<int> path;

	void backtracking(vector<int> candidates, int target, int count, int startIndex, vector<bool>& used) {
//		if (count > target) return fasle;
		if (count == target) {
			res.push_back(path);
			return; 
		}
		
		for (int i = startIndex; i < candidates.size() && count + candidates[i] <= target; i++) {
			if (i > 0 && candidates[i] == candidates[i - 1] && used[i - 1] == false) {
				continue;
			}
			count += candidates[i];
			used[i] = true;
			path.push_back(candidates[i]);
			backtracking(candidates, target , count, i + 1, used);
			used[i] = false;
			count -= candidates[i];
			path.pop_back();
		}
	}
	vector<vector<int>> findSameArray(vector<int>& candidates, int target) {
		vector<bool> used(candidates.size(), false);
		res.clear();
		path.clear();
		sort(candidates.begin(), candidates.end());
		backtracking(candidates, target, 0, 0, used);
		return res;
	}
}; 

int main() {
	vector<int> candidates = {1, 5, 2, 8};
	int sum = 0;
	for (int i = 0; i < candidates.size(); i++) {
		sum += candidates[i];
	}
	if (sum % 2 == 1) return false;
	int target = sum / 2;
	Solution solution;
	vector<vector<int>> res =  solution.findSameArray(candidates, target);
//	int m = res.size(), n = res[0].size();
	for (int i = 0; i < res.size(); i++) {
		for (int j = 0; j < res[0].size(); j++) {
			cout<< res[i][j]<< endl;
		}
	}
	return 0;
}

优化解法

思路及算法:使用动态规划,使用01背包解法,背包体积为sum / 2,背包要放入的商品(集合里的元素)重量为元素的数值,价值为元素的数值,背包如何正好装满,说明找到了总和为sum / 2的子集。背包中每一个元素不可重复放入。

1.确定dp数组以及下标的含义
dp[i]表示 背包总容量是i,最⼤可以凑成i的⼦集总和为dp[i]
2.确定递推公式
dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]);
3.dp数组如何初始化
⾮0下标的元素初始化为0就可以了,dp[0] = 0;
4.确定遍历顺序

// 开始 01背包
for(int i = 0; i < nums.size(); i++) {
 for(int j = target; j >= nums[i]; j--) { // 每⼀个元素⼀定是不可重复放⼊,所以从⼤到⼩遍历
 dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]);
 }
}

5.举例递归
在这里插入图片描述

//时间复杂度O(n ^ 2) 空间复杂度 O(n)
class Solution {
public:
 bool canPartition(vector<int>& nums) {
 int sum = 0;
 // dp[i]中的i表示背包内总和
 // 题⽬中说:每个数组中的元素不会超过 100,数组的⼤⼩不会超过 200
 // 总和不会⼤于20000,背包最⼤只需要其中⼀半,所以10001⼤⼩就可以了
 vector<int> dp(10001, 0);
 for (int i = 0; i < nums.size(); i++) {
 sum += nums[i];
 }
 if (sum % 2 == 1) return false;
 int target = sum / 2;
 // 开始 01背包
 for(int i = 0; i < nums.size(); i++) {
 for(int j = target; j >= nums[i]; j--) { // 每⼀个元素⼀定是不可重复放
⼊,所以从⼤到⼩遍历
 dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]);
 }
 }
 // 集合中的元素正好可以凑成总和target
 if (dp[target] == target) return true;
 return false;
 }
};

总结

通过分割相同子集的思考,能自然的想到使用回溯法求解所有解,使用动态规划的方法能减小时间复杂度,进而使用滚动数组来减小空间复杂度。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值