题目描述:输入一个整数数组和一个整数,在数组中查找一对数,满足它们的和正好等于输入的那个整数,并输出任一一对值。
解法一:直接穷举:
双层循环,复杂度为O(n^2);
#include <iostream>
using namespace std;
void DirectEnum(int nums[], int n, int len){
int i, j, flag = 0;
for(i = 0; i < len; ++i){
for(j = i+1; j < len; ++j){
if(nums[i] + nums[j] == n){
cout << nums[i] << " " << nums[j] << endl;
flag = 1;
break;
}
}
if(flag) break;
}
}
int main(){
int n, nums[20] = {4, 5, 23, 12, 29, 20, 14, 24, 34, 36, 39, 28, 33, 7, 9, 17, 27, 30, 8, 11};
while(cin >> n)
DirectEnum(nums, n, 20);
return 0;
}
解法二:排序后查找:
先对数组sort排序,之后遍历数组,对于每一个遍历的数x,二分查找n-x是否在该数组中,时间复杂度为O(nlogn)+O(n*logn)=O(nlogn);
#include <iostream>
#include <algorithm>
using namespace std;
bool Division(int nums[], int pos, int x, int low, int high){
int mid = (low + high)/2, tag = 0;
while(low <= high){
if(nums[mid] < x){
low = mid + 1;
mid = (low + high)/2;
}
else if(nums[mid] > x){
high = mid - 1;
mid = (low + high)/2;
}
else{
tag = 1;
break;
}
}
if(tag == 1 && mid != pos)//需要是一对数,故不能为它本身
return true;
return false;
}
void SortEnum(int nums[], int n, int len){
int i, j;
sort(nums, nums+len);
for(i = 0; i < len; ++i){
if(Division(nums, i, n-nums[i], 0, len-1)){
cout << nums[i] << " " << n-nums[i] << endl;
break;
}
}
}
int main(){
int n, nums[20] = {4, 5, 23, 12, 29, 20, 14, 24, 34, 36, 39, 28, 33, 7, 9, 17, 27, 30, 8, 11};
while(cin >> n)
SortEnum(nums, n, 20);
return 0;
}
解法三:散列排序:
假设所有的整数介于0~100间,以时间换空间,设置hash[n],每输入一个数x,就令hash[x]=1。判断时只需遍历一遍数组,看是否对应的hash[n-s[i]]为1即可,时间复杂度为O(n),针对空间复杂度,假设数组中有些数大于n,此时没必要给这些数设置hash空间了,因为它们不可能与另外的数累加得到n,故空间复杂度也为O(n);
#include <iostream>
using namespace std;
void HashEnum(int nums[], int hash[], int n, int len){
int i;
for(i = 0; i < len; ++i){
if((n-nums[i] >= 0) && hash[n-nums[i]]){
if((n-nums[i] == nums[i]) && (hash[nums[i]] == 1)) //去掉nums[i]为其本身的情况
continue;
cout << nums[i] << " " << n-nums[i] << endl;
break;
}
}
}
int main(){
int hash[200], n, nums[20] = {4, 5, 23, 12, 100, 22, 14, 24, 34, 36, 39, 28, 33, 7, 9, 17, 27, 30, 8, 11};
while(cin >> n){
memset(hash, 0, sizeof(hash));
int i;
for(i = 0; i < 20; ++i){
++hash[nums[i]];
}
HashEnum(nums, hash, n, 20);
}
return 0;
}//此时输入200,无输出
解法四:排序夹逼:
若此时数组是有序的,则可令时间复杂度为O(n),若是无序的,时间复杂度为O(nlogn) + O(n),空间复杂度都为O(1)。设置两个指针begin和end分别指向数组尾端,分别向右向左移动,每次依据a[begin]+a[end]的值与n进行比较,若该值小于n,说明需要a[begin]太小,begin指针需右移;若该值大于n,说明a[end]较大,end指针需左移。
#include <iostream>
#include <algorithm>
using namespace std;
void TwoSum(int nums[], int n, int len){
sort(nums, nums+len);
int begin = 0, end = len - 1;
while(begin < end){
if(nums[begin] + nums[end] == n){
cout << nums[begin] << " " << nums[end] << endl;
break;
}
else{
if(nums[begin] + nums[end] < n)
++begin;
else
--end;
}
}
}
int main(){
int n, nums[20] = {4, 5, 23, 12, 100, 22, 14, 24, 34, 36, 39, 28, 33, 7, 9, 17, 27, 30, 8, 11};
while(cin >> n)
TwoSum(nums, n, 20);
return 0;
}
举一反三:
寻找树中和为定值的所有路径
输入一棵二叉树和一个整数,从树的根结点开始向下访问,一直到叶结点。如下,输入整数22和该二叉树,则打印两条路径:10-->12和10-->5-->7。
算法分析:二叉树可以用数组进行保存,可以先分配一个数组,如下的二叉树,从下标1开始存放根结点,将不存在的结点在数组中用-1存放。根据二叉树的性质,结点编号为i的左右孩子结点编号为2i, 2i+1,故可以利用图的深度遍历方式从根结点一直往下访问,遇到-1则比较并输出,如下:
#include <iostream>
#include <iomanip>
using namespace std;
int n, t, path[22], Tree[110], loc;//path[]存放路径
void dfs(int cur){
int i;
//临界条件,dfs中必须写在dfs调用前面,否则出现无穷递归。递归条件是当前结点为叶子结点
if((Tree[2*cur] == -1) && (Tree[2*cur+1] == -1)){
int sum = 0;
for(i = 0; path[i]; ++i)
sum += path[i];
if(sum == t){
for(i = 0; path[i]; ++i){
if(i == 0) cout << path[i];
else
cout << setw(3) << path[i];
}
cout << endl;
}
return;
}
//深度递归
for(i = 0; i <= 1; i++){
if(Tree[2*cur+i] != -1){ //孩子结点未被访问
path[loc++] = Tree[2*cur+i];
dfs(2*cur+i);
path[--loc] = 0;
}
}
}
int main(){
while(cin >> n >> t && n != 0){ //输入树高n,和判断为和的整数t
//输入二叉树的信息
int i;
for(i = 0; i <= (int)pow(2.0, n+1)-1; ++i)
Tree[i] = -1;
for(i = 1; i <= (int)pow(2.0, n)-1; ++i)
cin >> Tree[i];
//开始深度遍历
loc = 0;
path[loc++] = Tree[1];
dfs(1);
}
return 0;
}
3个数和为定值问题:以解法三为例,对于每个数a,则对应另外两个数的和为-a,之后再进行遍历,对于遍历到的每个数b,看对应hash[-a-b+offset]是否为1,时间复杂度为O(n^2);
用解法一效仿,时间复杂度为O(n^3);用解法二做,为O(nlogn)+O(n*n*logn)=O(n^2logn);用解法四做,为O(nlogn)+O(n*n)=O(n^2)。最好分配一个标记数组来数本身去掉重复的情况。