尋找和爲定值的兩個數
編程之美2.12節(P176)
題目描述:能否快速找出一個數組中的兩個數字,讓這兩個數字之和等於一個給定的值,爲了簡化,假設這個數組肯定存在這兩個數。
例如數組1,2,4,7,11,15。給定值爲15,4+11 = 15,輸出4,11。
分析:
1,判斷條件就是兩數之和爲sum,對於A[i] ,需要找sum-A[i]是否也在數組A中,這樣就變成了一個查找算法,那麼提高查找效率就能提高整個算法的效率,通常可以先將數組排序,然後用二分查找等方法進行查找,查找時間爲O(logN),排序需要O(N * logN),遍歷需要O(N),總的時間爲O(N* logN + N* logN) = O (N *logN)。
2,如果能再縮短查找時間,算法的效率就會再次提高,我們可以使用hash表,它是可以在常數時間執行插入,刪除,查找。
這樣可以在時間O(N)完成,不過空間複雜度變爲O(N),某些情況下,空間換時間也不失爲一個好的方法。
3,感覺排序和查找有時候就是並存的,看到問題尤其是數的時候,先想一想,如果這些數有序是不是對於解決問題有幫助,對於這個問題,先排序,從小到大排列,然後用兩個首尾指針,i 從0,J從n-1開始,逐次判斷a[i]+a[j]?= sum,如果a[i]+a[j] > sum,想辦法讓兩者之和減小,尾端的是大數,所以就j–,i不動,如果a[i]+a[j] < sum,想辦法讓兩者之和增大,左邊爲小數,j不動,i++。這樣查找可以在O(N)完成,而且空間複雜度爲O(1),加上排序的時間O(N*logN),總的時間爲O(N*logN),兩個指針兩端掃描法。
實現:
#include<iostream>
#include <string.h>
#include <algorithm>
#define SIZE 10
#define TABLESIZE 16
using namespace std;
typedef struct Listnode * Position;
Position HashTable[TABLESIZE]; //這個地方直接用的數組,沒有用結構體,顯得不夠專業,不過方便喲
struct Listnode
{
int num;
Position next;
};
/*
專業的再構建一個結構體,裏面有hashtable大小和指向指向結構體Listnode的指針的指針
struct Hash
{
int Tablesize;
Position * TheLists;
};
*/
//二分查找法
int binary_search(int * a,int len,int goal)
{
int low = 0;
int high = len-1; //這個地方要注意,如果high= len,後面的也要更改
while(low <= high)
{
int middle = (high-low)/2 + low;
if(a[middle] == goal)
return 1;
else if(a[middle] > goal)
high = middle-1; //如果high= len,此處high = middle
else
low = middle+1;
}
return 0;
}
//簡單的hash函數
int hash_function(int num)
{
int n ;
n = num % TABLESIZE;
return n;
}
//在hashtable查找函數
Position Find(int num)
{
if(num <0) //如果值爲負,sum-a[i]的結果,說明肯定不是
return NULL;
else
{
int index = hash_function(num);
Position p = HashTable[index];
while(p != NULL && p->num != num)
p = p->next;
return p;
}
}
//hashtable插入函數
void Insert(int num)
{
int index = 0;
Position pos;
index = hash_function(num);
pos = Find(num);
if(pos == NULL) //如果在hashtable中不存在這個值,新插入一個
{
Position newcell = new Listnode;
newcell->num = num;
newcell->next = HashTable[index];
HashTable[index] = newcell;
}
}
//思路3,雙指針掃描法
void Find_num(int * a,int len,int sum)
{
int i,j;
for(i = 0,j = len-1;i<j;)
{
if(a[i]+a[j] == sum)
{
cout << "find num " << a[i] << " " << a[j] << endl;
return;
}
else if(a[i]+a[j] > sum)
{
j--;
}
else
{
i++;
}
}
cout << "not find" << endl;
}
int main()
{
int a[SIZE] = {2,4,1,23,5,76,0,43,24,65};
int len ;
int sum = 24;
sort(a,a+SIZE); //直接使用庫函數排序,排序不是重點
int i;
//二分查找法
for(i = 0;i< SIZE;i++)
{
cout << a[i] << endl;
if(binary_search(a,SIZE,sum-a[i]))
{
cout << "find num :" << a[i] << " " << sum-a[i] << endl;
break;
}
}
//hashtable
for(i = 0;i< SIZE;i++)
{
Insert(a[i]);
}
for(i = 0;i<SIZE;i++)
{
if(Find(sum-a[i]) != NULL)
{
cout << "find num :" << a[i] << " " << sum-a[i] << endl;
break;
}
}
//雙指針法
Find_num(a,SIZE,sum);
return 0;
}
想了想還是按標準的c風格把hashtable的解法寫了一遍,初始化的時候忘記返回了,一直段錯誤找不到,找了半個小時,哎,寫的時候一定要注意返回值問題。
/*************************************************************************
> File Name: find_twonum_hash.cpp
> Author: zxl
> mail: [email protected]
> Created Time: 2016年04月15日 星期五 09時42分14秒
************************************************************************/
#include <iostream>
#include <algorithm>
#include <stdlib.h> //支持malloc
using namespace std;
const int size = 10;
typedef struct Listnode * Position;
typedef struct Hash * HashTable;
typedef Position List;
struct Listnode
{
int num;
Position next;
};
struct Hash
{
int Tablesize;
List * TheLists;
};
//比較Low逼的hash函數
int Hash_function(int key,int size)
{
int n;
n = key % size;
return n;
}
bool isprime(int n)
{
int i;
if(n< 2) return false;
if(n== 2) return true;
for(i = 3;i*i <=n;i+=2) //如果不是素數,存在一個因子d,1< d < sqrt(n)
{
if(n%i == 0)
return false;
}
return true;
}
//找到比n大的下一個素數
int NextPrime(int n)
{
int i;
while(n++)
{
if(isprime(n))
return n;
}
}
//hashtable的初始化
HashTable init(int Tablesize)
{
HashTable H;
int i;
H = (Hash *)malloc(sizeof(Hash));
if(H == NULL)
cout << "out of space" << endl;
H->Tablesize = NextPrime(Tablesize);
H->TheLists = (List *)malloc(sizeof(List) * H->Tablesize);
if(H->TheLists == NULL)
cout << "out of space"<< endl;
for(i = 0;i< H->Tablesize;i++)
{
H->TheLists[i] =(Listnode *)malloc(sizeof(struct Listnode));
if(H->TheLists[i] == NULL)
cout << "out of space" << endl;
else
H->TheLists[i]->next = NULL;
}
return H; //尼瑪,這個地方忘記返回了
}
//在hash中查找key
Position Find(HashTable H,int key)
{
List L;
Position P;
int index;
index = Hash_function(key,H->Tablesize);
L = H->TheLists[index];
P = L->next;
while(P != NULL && P->num != key)
P = P->next;
return P;
}
//插入key
void Insert(HashTable H,int key)
{
Position pos,newcell;
List L;
pos = Find(H,key);
int index;
index = Hash_function(key,H->Tablesize);
if(pos == NULL) //如果不存在
{
newcell = (Listnode *)malloc(sizeof(struct Listnode));
L = H->TheLists[index];
newcell->num = key;
newcell->next = L->next;
L->next = newcell;
}
}
int main()
{
int a[size] = {2,4,1,23,5,76,0,43,24,65};
const int sum = 24;
int Tablesize = 16;
sort(a,a+size);
int i;
HashTable H;
H = init(Tablesize);
for(i = 0;i<size;i++)
Insert(H,a[i]);
for(i = 0;i<size;i++)
{
if(sum-a[i] < 0)
continue;
else
{
if(Find(H,sum-a[i]) != NULL)
{
cout << "find num:" << a[i] << " " << sum-a[i] << endl;
break;
}
}
}
return 0;
}
尋找和爲定值的多個數
題目描述:
輸入兩個整數n和m,從數列1,2,3…n中隨意取幾個數,使其和等於m,要求將其中所有的可能組合列出來。
分析:
有點類似0-1揹包問題:在選擇每個數的時候,對每個數只用兩種選擇,要麼裝入到序列中,要不裝,不能將一個數裝入多次。
可以使用遞歸的思想:放入數n,問題就變爲了n-1個數的和爲m -n,依次遞歸,不放入數n,問題就變爲了n-1個數和和爲m。
解法一:
/*************************************************************************
> File Name: find_manynum.cpp
> Author: zxl
> mail: [email protected]
> Created Time: 2016年04月18日 星期一 10時38分47秒
************************************************************************/
#include <iostream>
#include <list>
using namespace std;
list<int > List;
void find_factor(int sum,int n)
{
if(n<=0 || sum<= 0)
return;
if(sum == n)
{
List.reverse(); //由大到小輸出
list<int>::iterator iter;
for(iter = List.begin();iter !=List.end();iter++)
{
cout << *iter << "+";
}
cout << n << endl;
List.reverse();
}
List.push_front(n); //將n放入到序列中
find_factor(sum-n,n-1);
List.pop_front(); //n不放入到序列中
find_factor(sum,n-1);
}
int main()
{
int sum ,n;
cout << "input the sum" <<endl;
cin>>sum;
cout << "input the n" << endl;
cin>> n;
find_factor(sum,n);
return 0;
}
回溯法解問題,定義問題的解空間。確定瞭解空間的組織結構後,從開始結點出發,以深度優先方式搜索整個解空間,通常採用兩種策略避免無效搜索,提高回溯法的搜索效率。其一是用約束函數在擴展結點處剪去不滿足約束的子樹;其二是用限界函數剪去得不到最優解的子樹,這兩類函數統稱爲剪枝函數。
X數組爲解向量,t = ∑(1,…k-1)Wi*Xi,r = ∑(k,…n)Wi
Wi表示對應結點的權重,在此題就是i,Xi爲對應的向量值(0或者1),
若t+Wk+W(k+1)<=M,則Xk = 1,遞歸k結點的左兒子(X1,X2…X(k-1),1);否則剪枝;
若t+r-Wk>=M && t+W(k+1) <= M,則置Xk = 0;遞歸k結點的右兒子(X1,X2,…,X(k-1),0);否則剪枝;
/*************************************************************************
> File Name: find_manysum_hui.cpp
> Author: zxl
> mail: [email protected]
> Created Time: 2016年04月18日 星期一 15時32分47秒
************************************************************************/
#include <iostream>
#include <stdlib.h>
#include <string.h>
using namespace std;
void sumofsub(int t,int r,int k,int M,bool&flag,bool *X)
{
X[k] = true;
if(t+k == M)
{
int i;
flag = true;
for(i = 0;i<k;i++)
{
if(X[i] == true)
cout << i << "+" ;
}
cout <<k << endl;
}
else
{
if(t+k+k+1 <= M)
sumofsub(t+k,r-k,k+1,M,flag,X); //遞歸左子樹
if((t+r-k>= M) && (t+k+1)<=M)
{
X[k] = false;
sumofsub(t,r-k,k+1,M,flag,X); //遞歸右子樹
}
}
}
void search(int n,int m)
{
bool * X = (bool *)malloc(sizeof(bool)*(n+1));
memset(X,false,sizeof(bool)*(n+1)); //將x中的值設置爲false
int sum = (n+1)*n/2; //前n項和
if(1>m || sum < m)
{
cout << "m is error" << endl;
return;
}
bool f = false;
sumofsub(0,sum,1,m,f,X);
if(!f)
{
cout << "not found" << endl;
}
free(X);
}
int main()
{
int n,m;
cout << "input N" << endl;
cin >> n;
cout << "input M" << endl;
cin >> m;
search(n,m);
return 0;
}