要求时间复杂度是O(n)。如果有多对数字的和等于输入的数字,输出任意一对即可。
例如输入数组1、2、4、7、11、15和数字15。由于4+11=15,因此输出4和11。
分析:
思路一:排序,两头向中间遍历,两头和大于sum右边移动←,小于sum左边移动->,相等,找到输出,否则退出。
时间复杂度,排序O(nlogn),快排,两头遍历,O(n)。
void findsummand_01(int array[],const int n,const int sum)
{
int *addend = new int[n];
for(int i=0;i<n;i++)
{
addend[i] = sum - array[i];
}
for(int k=0,j=n-1;k<=j;)
{
if(array[k]==addend[j])
{
cout<<array[k]<<"+"<<array[j]<<"="<<sum<<endl;
return;
}
if(array[k]<addend[j])
k++;
else
j--;
}
cout<<"Not Find !"<<endl;
}
思路二:hash表,首先建立hash表,对数组遍历,查看另一个加数是否在hash表中,在输出,否则退出
时间复杂度:O(n),空间复杂度O(N)
const int size = 30;
typedef struct node *ptr_node;
ptr_node head[size];
struct node
{
int data;
ptr_node next;
};
int hash_function(int data)
{
return data % size;
}
void append_data(const int data)
{
int index = hash_function(data);
ptr_node p = head[index];
while(p!=NULL)
{
if(p->data == data)
return ;
p=p->next;
}
ptr_node q = new node();
q->data=data;
q->next=head[index];
head[index]=q;
}
bool find_data(const int data)
{
int index = hash_function(data);
ptr_node p = head[index];
while(p!=NULL)
{
if(p->data == data)
return true;
p=p->next;
}
return false;
}
void findsummand_02(int array[],const int n,const int sum)
{
for(int i=0;i<n;i++)
{
int addend = sum - array[i];
if(find_data(addend))
{
cout<<array[i]<<"+"<<addend<<"="<<sum<<endl;
return ;
}
}
cout<<"Not Find!"<<endl;
}
扩展问题
- 如果在返回找到的两个数的同时,还要求你返回这两个数的位置列?
- 如果需要输出所有满足条件的整数对呢?
- 如果把题目中的要你寻找的两个数改为“多个数”,或任意个数列?
对于第二个问题扩展,在两边扫描找到第一组整数对时,不退出,继续,直到扫描完所有数据。
对于第三个问题扩展,可以由《输入两个整数 n 和 m,从数列1,2,3.......n 中 随意取几个数,使其和等于 sum ,要求将其中所有的可能组合列出来。》演变得来,这个基本题目的解法是使用0-1背包问题解法思路,
1.首先判断,如果和为0或负数,数列为非正数,返回;
2.将最大数n加入且n == m,则满足条件,输出;
3.将n分两种情况求解,(1)n没有加入,取n = n - 1; m = m;递归下去;(2)n加入,取n = n - 1,m = m - n,递归下去
使用递归的解法,代码如下
#include<list>
#include<iostream>
using namespace std;
list<int>list1;
void find_factor(int sum, int n)
{
// 递归出口
if(n <= 0 || sum <= 0)
return;
// 输出找到的结果
if(sum == n)
{
// 反转list
list1.reverse();
for(list<int>::iterator iter = list1.begin(); iter != list1.end(); iter++)
cout << *iter << " + ";
cout << n << endl;
list1.reverse();
}
list1.push_front(n); //典型的01背包问题
find_factor(sum-n, n-1); //放n,n-1个数填满sum-n
list1.pop_front();
find_factor(sum, n-1); //不放n,n-1个数填满sum
}
int main()
{
int sum, n;
cout << "请输入你要等于多少的数值sum:" << endl;
cin >> sum;
cout << "请输入你要从1.....n数列中取值的n:" << endl;
cin >> n;
cout << "所有可能的序列,如下:" << endl;
find_factor(sum,n);
return 0;
}
则扩展题目的代码如下:
list<int> list2;
void findSum(int array[],int i,int sum)
{
if(sum<=0 || i<0)
return;//递归出口
if(sum==array[i])
{
for(list<int>::iterator iter = list2.begin();iter!=list2.end();iter++)
cout<<*iter<<" + ";
cout<<array[i];
cout<<endl;
}
list2.push_front(array[i]);
findSum(array,i-1,sum-array[i]);
list2.pop_front();
findSum(array,i-1,sum);
}
举一反三
1、在二元树中找出和为某一值的所有路径 输入一个整数和一棵二元树,从树的根结点开始往下访问一直到叶结点所经过的所有结点形成一条路径,然后打印出和与输入整数相等的所有路径。 例如输入整数22和如下二元树
10
/ \
5 12
/ \
4 7
则打印出两条路径:10, 12和10, 5, 7。 其中,二元树节点的数据结构定义为:
struct BinaryTreeNode // a node in the binary tree
{
int m_nValue; // value of node
BinaryTreeNode *m_pLeft; // left child of node
BinaryTreeNode *m_pRight; // right child of node
};
2、有一个数组a,设有一个值n。在数组中找到两个元素a[i]和a[j],使得a[i]+a[j]等于n,求出所有满足以上条件的i和j。
3、3-sum问题
给定一个整数数组,判断能否从中找出3个数a、b、c,使得他们的和为0,如果能,请找出所有满足和为0个3个数对。
4、4-sum问题
给定一个整数数组,判断能否从中找出4个数a、b、c、d,使得他们的和为0,如果能,请找出所有满足和为0个4个数对。
一般性的题目:请编写一个程序,当纸片上所写的数字是k1,k2,k3,k4,..,kn时,是否存在抽取4次和为m的方案(抽完放回),如果存在,输出YES;否则,输出NO。
最容易想到的方案是用4个for循环直接穷举所有方案,时间复杂度为O(N^4)
//通过4重for循环枚举所有方案
for (int a = 0; a < n, a++)
{
for (int b = 0; b < n; b++)
{
for (int c = 0; c < n; c++)
{
for (int d = 0; d < n; d++)
{
if (k[a] + k[b] + k[c] + k[d] == m)
{
f = true;
}
}
}
}
}
再次基础上进行改进
对于最内层d,是找到一个数使得ka+ kb +kc + kd = m,即在k[]中找到一个数x=m-ka-kb-ka ,因此接下来要做的就是查看x是否存在于k中,可先将k排序,然后用二分查找。更近一步,对于c,d两层,需要检查是否有满足kc + kd = m - ka -kb,同样可以枚举出kc+kd的所得的(n^2)结果并排序,再用二分查找求解。