求满足条件的和

在博客http://blog.csdn.net/linyunzju/article/details/7720413中,

问题:

1. 快速找出一个数组中的两个数,让这两个数之和等于一个给定的值。

2. 快速找出一个数组中的三个数,让这三个数之和等于一个给定的值。

1. 解法:算法复杂度为O(nlogn)。先用快速排序对数组排序,让后用双指针(双索引)法对排序好的数组进行反向遍历,并且遍历的方向不变。(若是计算两个数的和,则初始化为i=0,j=n-1,若是计算两个数的差,则初始化为i=0,j=1)

之所以这样遍历方式能成功,是因为排序后,若ai+aj<sum,则ai+ak<sum(k=i,i+1...j),而i之前j之后的情况已遍历过,所以只有i++才可能有等号的情况;若ai+aj>sum,则ak+aj>sum(k=i,i+1...j),而i之前j之后的情况已遍历过,所以只有j--才可能有等号的情况。

代码如下:
// 三个数和.cpp : 定义控制台应用程序的入口点。
//
/*author :songzhengchao
time:2013/10/29
function:sum_2 有序数组的找出2个元素之和为sum 
sum_3 在有序数组元素组找出3个之和为sum
sum_21  在有序数组中找出2个元素之和离sum 的差最小的元素

*/

#include "stdafx.h"
#include <iostream>
using namespace std;
void sum_2(int arr[],int n,int sum)//n元素的个数
{
	int i=0,j=n-1;
	while(i<j)
	{
		int plus=arr[i]+arr[j];
		if(plus==sum)
		{
			cout<<arr[i]<<"---"<<arr[j]<<endl;
			i++;j--;
			
		}else if(plus>sum){
			j--;
		}else
		{
			i++;
		}
	}

}
void sum_3(int arr[],int n,int sum)
{
	int i,j,k;
	for(k=0;k<=n-1;k++)
	{
		i=0;j=k;
		//if(k==i) i++;
		//if(k==j) j--;
		int sum_2=sum-arr[k];
		while(i<=j)
		{
			int plus=arr[i]+arr[j];
			if(plus==sum_2){
				cout<<arr[i]<<"---"<<arr[j]<<"--"<<arr[k]<<endl;
				i++;j--;
			}else if(plus>sum_2){
				j--;
			}else{
				i++;
			}
			//if(k==i) i++;
			//if(k==j) j--;
		}
	}
}
void sum_21(int arr[],int n,int sum)//n元素的个数
{
	int i=0,j=n-1;
	int i_index,j_index;
	int min=123;
	while(i<j)
	{
		int plus=arr[i]+arr[j];
		int delta=abs(plus-sum);
		
		if(plus==sum)
		{
			cout<<arr[i]<<"---"<<arr[j]<<endl;
			i++;j--;
			
		}else if(plus>sum){
			if(min>delta)
			{
				min=delta;
				i_index=i;
				j_index=j;
			}
				
			j--;
		}else
		{
			if(min>delta)
			{
				min=delta;
				i_index=i;
				j_index=j;
			}
			i++;
		}
	}
	cout<<arr[i_index]<<"---"<<arr[j_index]<<endl;
	cout<<min<<endl;
}

int _tmain(int argc, _TCHAR* argv[])
{
	int arr[]={1,2,3,4,5,6,7,8,9};
	sum_21(arr,9,20);
	return 0;
}
其中,sum_21借鉴了http://blog.csdn.net/xiongjinshui/article/details/7934256中的

2、如果完全相等的一对数字找不到,能否找出和最接近的解?

②两个下标夹逼思路:

同样是先排序,然后取收尾两个下标i=0,j=N-1,求和并保存其误差,若误差为正,则j--,若误差为负,则i++。在满足i<j的条件下,如此循环下去,找出误差绝对值最小的一组ij即可。注意,这里不需要存储所有的误差,只需要存储当前遍历的i,j中误差最小值及相应的i,j即可

还有问题: 如何找出任何个数的和为sum,如何做,这是NPC问题,

我用回溯法解决的

用回溯法解决子集和问题
问题描述:子集和问题的一个<S,c>,其中S={s1,s2........sn}是一个正整数的集合,c是一个正整数。子集和问题判定是否存在S的一个子集S1,使得S1中元素的和为c
子集和问题 在算法导论中说是NP问题,没有多项式时间可以解决的。
回溯法是有条件的枚举法
一般的,如果搜索到一个节点,而这个节点不是叶节点,并且满足约束条件和目标函数的界,同时,这个节点的所有儿子节点还未完全搜索完毕,就把这个节点成为L节点(活)
把当前正在搜索其儿子节点的节点,成为e_节点(扩展节点),e_节点也必然是一个L节点
把不满足条件或目标函数的节点,或其儿子节点已全部搜索完毕的节点,或者叶子节点,统称d_节点(死节点)(出自算法设计与分析)

本问题中,我打算左孩子是X[i]=1,右孩子x[i]=0
对于d_节点,遍历兄弟节点(若有兄弟节点的话),否则往上遍历,直到有兄弟节点为止

while(x[i]==0&&i>=0) i--;/*往上遍历直到x[i]=1&&i>=0(不能超过第0层了)*/
if(i<0) return;
else
x[i]=0;/*访问右孩子*/
对于l_节点,只需 i+=1;x[i]=1;

这个问题中 d_节点是 叶子节点(i==n) 和为c  或 和>c 都要遍历右兄弟 或往上遍历
// subset.cpp : 定义控制台应用程序的入口点。

//

/*******************************

name:subset.cpp

write by :song zheng chao

function:input the array int arr[]

and sum;

回溯法













********************************/

#include "stdafx.h"

#include <iostream>

using namespace std;

int sum123(int x[],int arr[],int k)  /计算 x[0]*arr[0]+.....+x[k]*arr[k] 当前的值/

{

	int sum1=0;

	for(int i=0;i<=k;i++)

	{

	    sum1+=x[i]*arr[i];

	}

	return sum1;

}







void  fun(int arr[],int n,int sum)//下标从0......n

{

	int i=0;

	int x[125];

	/*initial x*/

	for(i=0;i<=n;i++)

	{

		x[i]=-1;

	}




	i=0;x[0]=1;//初始话

	

	

	while(i>=0)

	{

		while(x[i]==0||x[i]==1)//  x[i]解的范围

		{

			




		if(i==n||sum123(x,arr,i)>=sum) //d_节点,要遍历兄弟节点,或往上遍历了

		{

			if(sum123(x,arr,i)==sum)

			{

				int j=0;

				for(;j<=i;j++)

				{

					if(x[j]==1)

						cout<<arr[j]<<" ";

					

				}cout<<endl;

			}

			while(x[i]==0&&i>=0) i--;

			if(i<0) return;

			else

			x[i]=0;

		

		}else  //l_节点

		{i++;x[i]=1;}




		}




		

	}

		

	

		

}













int _tmain(int argc, _TCHAR* argv[])

{

	int arr[]={1,5,3,4,6,2};

	 fun(arr,5,7);




	return 0;

}

图片



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值