在博客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问题,我用回溯法解决的
![](http://edu.qzs.qq.com/ac/b.gif)
回溯法是有条件的枚举法
一般的,如果搜索到一个节点,而这个节点不是叶节点,并且满足约束条件和目标函数的界,同时,这个节点的所有儿子节点还未完全搜索完毕,就把这个节点成为L节点(活)
把当前正在搜索其儿子节点的节点,成为e_节点(扩展节点),e_节点也必然是一个L节点
把不满足条件或目标函数的节点,或其儿子节点已全部搜索完毕的节点,或者叶子节点,统称d_节点(死节点)(出自算法设计与分析)
本问题中,我打算左孩子是X[i]=1,右孩子x[i]=0
对于d_节点,遍历兄弟节点(若有兄弟节点的话),否则往上遍历,直到有兄弟节点为止
对于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;
}
![图片](https://i-blog.csdnimg.cn/blog_migrate/849196ee7beb0cc21cda17f0c652297b.jpeg)