动态规划简单dp之判断一个数组中是否可以组成和为给定的数

这篇博客探讨了如何运用动态规划算法判断一个数组中是否能找出子集,其元素之和等于给定的目标数值。通过非递归的方法,借助图表辅助理解,详细解析了动态规划的解题思路。
#include<iostream>
#include<cmath>
using namespace std;

/**
 
判断一个正数 数组中的数是否可以组合起来 为一个给定的值 如果可以返回true   
*/


const int N=1010;

int a[N];

int dp[N][100];

//递归解法  从小标0开始到达i 判断是否可以有构成和为的s数 
bool fun(int i,int s){
	
	if(s==0) return true;
	
	else if(i==0)//到达第一个 
		return a[0]==s;//如果第0个刚好等于s 则返回true  不行则返回false
	else if(a[i]>s)//下标为i a[i]已经大于s 则不选
		return fun(i-1,s);
	else{
		
		//判断选与不选a[i] 
		bool t1=fun(i-1,s-a[i]);//这是选择了a[i]
		bool t2=fun(i-1,s);//不选
		return t1||t2; 
	}
	
}

//非递归解法 构建一个二维数组 dp[i][s] 表示从0到达i的时候 能否构建成和为s的真假值 

void fun2(int n,int s){
	
	
	for(int j=0;j<=s;j++)
	{
		dp[0][j]=0; 
	}
	//比如第0个的值为3  因为我们的dp[0][3] 表示从0到0是否可以组成3  所以dp[0][3] 直接为true 
	if(a[0]<=s)//当第0个已经比s要大 则显然无法来构建s 只能是小于s的时候
		dp[0][a[0]]=1; 
		 
	for(int i=0;i<n;i++)
		dp[i][0]=1;//当dp[i][j] 即要给定的值为0时 表示无需其他值来构建了 目前已完成则就是true了  
	
	//a[i][j]  表示从a[0] 到a[i] 是否可以组成j 的真假值
	//i从a[0]-a[n-1] j从0到给定值s	
	for(int i=1;i<n;i++){
		for(int j=1;j<=s;j++){
			if(a[i]>j)// a[i] 已经比j还要大了,很显然这个时候不选则a[i] 
				dp[i][j]=dp[i-1][s];
			else{
				int t3= dp[i-1][j-a[i]];//选择了a[i] 
				int t4=dp[i-1][j];//不选a[i] 
				dp[i][j]=(t3||t4);
			}
				
		}
	}
		
}

int main(void){
	
	int n,m;
	cin>>n>>m;
	for(int i=0;i<n;i++)
		cin>>a[i];
		 
	
//	if(fun(n-1,m)) 
//		cout<<"true"<<endl;
//	else
//		cout<<"false"<<endl;


	fun2(n,m);
	if(dp[n-1][m]) 
		cout<<"true"<<endl;
	else
		cout<<"false"<<endl;
} 

其中非递归的方式可以借助以下这张图来理解
在这里插入图片描述

<think>嗯,用户现在需要的是用C语言判断数组是否存在一个子集,使得子集的等于目标X。首先,我得确定这个问题的具体要求。用户提到给定数组array长度N,以及目标X,要判断是否存在这样的子集。这看起来像是一个子集问题,属于经典的动态规划问题。 那用户可能已经了解动态规划的基本概念,或者他们可能从之前的对话中知道动态规划与DFS的关系。不过,这里需要给出具体的C语言实现。首先,我需要考虑不同的解法,比如递归回溯、动态规划,或者是带有剪枝的优化方法。但由于数组长度N可能较大时,递归的效率会很低,所以动态规划可能是更优的选择。 用户可能需要一个高效的解决方案,尤其是当N较大的时候。因此,动态规划的时间复杂度是O(N*X),这在X不太大的情况下是可行的。但如果X非常大,这样的方法可能不够高效,但可能已经是较优的选择了。 接下来,我需要考虑如何构建动态规划表。通常,子集问题可以用一个二维数组dp[i][j]表示前i个元素是否组成j。或者优化空间复杂度,使用一维数组。这里可能用一维数组更节省空间。 然后,处理边界条件。比如,当目标X为0时,直接返回true,因为空集的就是0。如果数组中有元素大于X,这些元素可以直接跳过,因为它们不可能被包含在子集中。这可能作为优化步骤。 然后,编写代码的结构。主函可能包括输入处理,调用子集判断,并输出结果。需要处理数组的动态分配,但用户可能希望代码示例简洁,所以可能假设数组已经给定,或者用静态数组示例。 需要注意的是,当X超过数组所有元素的总时,可以直接返回false,这也是一个优化点。不过,在代码中是否需要加入这一步骤?可能要考虑用户的需求是否希望尽可能优化。 另外,测试用例的考虑。例如,数组中有负的情况如何处理?但通常子集问题假设元素都是非负的,否则问题会更复杂。用户的问题中没有说明元素的正负,可能需要假设为非负,或者在代码中加入处理负的情况。但根据常规子集问题,可能应假设为非负,否则动态规划的方法可能需要调整。 因此,在代码中可能需要添加检查数组元素是否为非负,或者处理负的情况。但用户的问题没有明确说明,可能需要假设为非负,或者询问用户,但根据问题描述,可能应处理非负情况。 综上,动态规划的实现步骤如下: 1. 处理特殊情况:X为0,直接返回true;如果数组中有元素等于X,直接返回true;如果X小于0,返回false(假设元素非负)。 2. 计算数组,如果总小于X,返回false。 3. 创建动态规划数组dp,大小为X+1,初始化dp[0]为true,其余为false。 4. 遍历数组中的每个元素,对于每个元素num,从X向下遍历到num,更新dp[j] = dp[j] || dp[j - num]。 5. 最后返回dp[X]。 这样,空间复杂度是O(X),时间复杂度是O(N*X)。 现在需要考虑如何将这些步骤转化为C语言代码。C语言中需要手动管理内存,所以需要动态分配dp数组。例如,使用布尔类型或者用整代替,因为C语言没有内置的bool类型(需要引入stdbool.h)。 可能的代码结构: - 函数组指针,数组长度N,目标X。 - 函返回bool类型。 - 在函内部,处理特殊情况,如X为0返回true。 - 计算数组,如果总小于X,返回false。 - 分配dp数组,大小为X+1。 - 初始化dp[0]为true。 - 遍历数组元素,更新dp数组。 - 最后检查dp[X]是否为true,释放dp内存,返回结果。 需要注意的是,当X为0时,是否应该直接返回true,或者需要考虑空集的情况。通常,空集的是0,所以当X=0时,确实存在这样的子集,即空集。但用户可能需要确认这一点,或者在问题中是否有特别说明X≥1。但根据常规,应该包括这种情况。 另外,如果数组中有元素等于X,可以直接返回true,这可能作为优化步骤。例如,在遍历数组时,如果某个元素等于X,可以直接返回true,避免后续计算。但这一步是否必要?这取决于实际据,如果数组中有这样的元素,可以立即返回,否则继续处理。可能这能提高效率,但需要额外的循环检查。 或者,在动态规划过程中自动处理这种情况,因为当num等于X时,在更新dp时会设置dp[X]为true,所以可以省略显式检查。不过显式检查可能在某些情况下更快。 现在将这些思考转化为代码: 首先,函定义: #include <stdbool.h> #include <stdlib.h> bool hasSubsetSum(int arr[], int N, int X) { // 处理特殊情况 if (X == 0) return true; if (N == 0) return false; // 空数组且X不为0 // 计算总并检查是否存在元素等于X int total = 0; for (int i = 0; i < N; i++) { if (arr[i] == X) return true; if (arr[i] < 0) return false; // 假设元素非负,否则需要不同处理 total += arr[i]; } if (total < X) return false; if (X < 0) return false; // 假设X非负 // 动态规划数组 bool *dp = (bool*)calloc(X + 1, sizeof(bool)); dp[0] = true; for (int i = 0; i < N; i++) { int num = arr[i]; for (int j = X; j >= num; j--) { if (dp[j - num]) { dp[j] = true; } } if (dp[X]) { // 提前终止 free(dp); return true; } } bool result = dp[X]; free(dp); return result; } 需要注意的问题: - 当数组元素可能为负时,上述方法将不适用,但用户问题中没有说明,所以假设元素为非负。 - 当X=0时,直接返回true,但根据问题描述,可能是否需要允许空集作为子集。如果问题中子集必须非空,则当X=0时需要返回false,除非数组中有0元素。但通常子集问题允许空集,所以这里保持原样。 - 在代码中,当发现元素等于X时,直接返回true,这样可以提前结束。 - 在动态规划循环中,每次处理一个元素num,从X到num逆序更新dp数组,防止重复使用同一元素多次(因为每个元素只能用一次?或者问题允许重复?)这里题目是子集,即元素不可重复使用,所以需要用0-1背包的方式,逆序更新。如果是允许重复使用,则是完全背包问题,顺序更新。 但问题中的子集应该是不重复使用的,因此逆序是正确的。这一点需要确认。例如,子集问题通常是指选择元素的子集,每个元素最多选一次,因此是0-1背包问题,所以逆序更新是正确的。 此外,当处理每个元素时,如果发现dp[X]已经为true,可以提前返回,减少计算量。 测试案例: 例如,数组为{3, 34, 4, 12, 5, 2}, X=9,应该存在子集{4,5},所以返回true。 另一个测试案例,数组为{1,2,3}, X=7,总为6,返回false。 另外,X=0时返回true。 数组包含负的情况如何处理?比如,用户是否考虑这种情况?但根据代码中的处理,如果发现数组中有负,会直接返回false,这可能不正确。例如,数组{-1, 2}, X=1,此时子集{-1,2}为1,应该返回true,但代码中因为元素有负而返回false。这说明当前代码仅处理非负元素的情况。如果用户可能输入负,则需要修改算法。但根据问题描述,用户可能没有说明,所以保持原样,或者需要注释说明。 综上,上述代码应该可以处理非负数组的情况,并且正确判断是否存在子集为X。因此,这应该是用户需要的答案。</think>以下是使用C语言解决**子集问题**的代码实现,基于**动态规划**方法,时间复杂度为$O(N \times X)$,空间复杂度为$O(X)$: --- ### 代码实现 ```c #include <stdio.h> #include <stdbool.h> #include <stdlib.h> bool hasSubsetSum(int arr[], int N, int X) { // 处理特殊情况:目标为0时,空集即为解 if (X == 0) return true; // 空数组或目标为负(假设元素非负) if (N == 0 || X < 0) return false; // 预检查:如果存在元素直接等于X,直接返回true for (int i = 0; i < N; i++) { if (arr[i] == X) return true; } // 计算数组,若总小于X则直接返回false int total = 0; for (int i = 0; i < N; i++) total += arr[i]; if (total < X) return false; // 动态规划表:dp[j]表示能否组成为j的子集 bool* dp = (bool*)calloc(X + 1, sizeof(bool)); dp[0] = true; // 空集的为0 // 遍历数组元素,更新dp表 for (int i = 0; i < N; i++) { int num = arr[i]; // 逆序更新防止重复使用同一元素 for (int j = X; j >= num; j--) { if (dp[j - num]) dp[j] = true; } // 提前终止:若dp[X]已为true,直接返回 if (dp[X]) break; } bool result = dp[X]; free(dp); return result; } // 测试用例 int main() { int arr[] = {3, 34, 4, 12, 5, 2}; int N = sizeof(arr) / sizeof(arr[0]); int X = 9; if (hasSubsetSum(arr, N, X)) { printf("存在为%d的子集\n", X); } else { printf("不存在为%d的子集\n", X); } return 0; } ``` --- ### 核心逻辑分步解析 1. **特殊情况处理**: - 若目标`X=0`,直接返回`true`(空集为0)。 - 若数组为空或`X<0`(假设数组元素非负),返回`false`。 2. **预检查优化**: - 遍历数组,若发现某个元素等于`X`,直接返回`true`。 3. **动态规划表初始化**: - 创建一维布尔数组`dp`,`dp[j]`表示是否存在子集为`j`。 - 初始化`dp[0] = true`(空集为0)。 4. **填表过程**: - 遍历每个元素`num`,**逆序**更新`dp`表(避免重复使用元素)。 - 若`dp[j - num]`为`true`,则`dp[j]`也设为`true`。 5. **提前终止**: - 若`dp[X]`变为`true`,提前终止循环。 --- ### 关键点说明 - **时间复杂度**:$O(N \times X)$,适用于`X`不太大的场景。 - **空间优化**:使用一维数组替代二维数组,空间复杂度从$O(N \times X)$优化为$O(X)$。 - **元素限制**:假设数组元素均为**非负整**,若包含负需修改逻辑。 - **应用场景**:资源分配、背包问题、组合优化等需判断子集存在性的问题。 --- ### 示例输出 ``` 存在为9的子集 ```
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值