標籤:演算法 子數組和 子集和 最大和
1.給定正整數N,列出所有總和為N的連續正整數串。
方法1:
可以用兩個數small,big分別表示序列的最小值和最大值。首先把small初始化為1,big初始化為2。如果從small到big的序列的和大於n的話,則右移small即從序列中移除最小的數;如果從small到big的序列的和小於n的話,則右移big,相當於向序列中添加下一個數字,一直到small等於(1+n)/2,保證序列中至少有兩個數。
方法2:
設最大長度為x(加起來等於同樣值的元素最多,則這些元素從最小的元素裡取),則
1+2+...+ x = N => x(x+1)/2 = N
=>x = sqrt(2N+1/4)-1/2 <= sqrt(2N)
設待求出子序列的長度為len ,則依次從len = x-->1找子序列就行了
設序列開始的位置的元素值為s,則對於長度為len的子序為
s,s+1,....s + x-1
該子序列的和為x*s + x(x-1)/2 = N => s = N/x - (x-1)/2
若s為整數,則s,s+1,.... s + x-1即為所求的合格子序列。
代碼(來自網上):
#include <stdio.h>#include <math.h>int main(){ int m=0, n=0, start=0, end=0, flag=0; //m為輸入的數,n為正整數序列個數,start為顯示的開始正整數,end為顯示的結束正整數, //flag表示有無合格序列存在,0(預設)沒有,1有 float temp=0.0; printf("please input a Integer:"); scanf("%d",&m); printf("\n"); n=(int) (sqrt((double)m)*sqrt((double)2)); //求出可能的最大的序列個數 while(n>=2) { temp=(float)m/n-(float)(n-1)/2; //求開始數 if(temp==(int) temp) { //判斷是不是正整數,即有沒有符合合格序列存在 for(flag=1,start=(int) temp,end=start+n;start<end;start++) { //flag標誌置1,有符合合格序列存在,得出開始整數start和結束整數end printf("%d",start); printf(" "); } printf("\n"); } n--; } if(flag==0) //沒有符合合格序列存在,則輸出"none" printf("none");}
2.和為S的兩個數
輸入一個遞增排序的數組和一個數字S,在數組中尋找兩個數,是的他們的和正好是S,如果有多對數位和等於S,輸出兩個數的乘積最小的。
思想:因為已經排好序,那麼如果存在那麼兩個數,可能是一個比較大,一個比較小,或者是比較相等的兩個數!所以我們設一個n1=min(即a[0]),n2=max(即a[n-1] )開始,實際是n1 = a[low],low=0開始,n2 = a[high],high=n-1開始,然後n1+n2與S(我們需要的和)進行比較,若>,則n2=a[--high];若<, n1=a[++low],若==,則使用mul儲存乘積,還要儲存兩個數,因為可能不只一對數,它只要乘積小的!掃描直到low>=high終止!
3.判斷一包含n個整數a[]中是否存在i、j、k滿足a[i] + a[j] = a[k]的時間複雜度最小值是:
A.O(n^2) B. O(n^2*logn) C. O(n^3) D. O(nlogn)
分析:先遞增排序(O(nlgn)),再固定每一個元素,按照求和為S的兩個數字S的方法求解(n*O(n)=O(n^2))
求和問題總結(leetcode 2Sum, 3Sum, 4Sum, K Sum)
http://blog.csdn.net/doc_sgl/article/details/12462151
曾經在網上見了一個文章討論該問題O(nlgn)的方法,可惜現在一直找不到了,大概是利用相反數之類的思想。
4.輸入兩個整數 n 和 m,從數列1,2,3.......n 中隨意取幾個數,使其和等於 m ,要求將其中所有的可能組合列出來。(0-1背包問題)
分析:
問題其實本質上就是0/1背包問題,對於每一個n,我們採用貪婪策略,先考察是否取n,如果取n,那麼子問題就變成了find(n-1,m-n),而如果捨棄n,子問題則為find(n-1,m)。
那麼,如何制定解的判定策略?我們知道,遞迴需要邊界條件,而針對背包問題,邊界條件只有兩種,如果n<1或者m<1,那麼便相當於“溢出”,無法combo出m,而另一種可能就是在剩餘的n個裡恰好滿足m==n,即此時背包剛好填充滿,輸出一組解單元。除此之外,再無其他。為了減小遞迴深度,當n>m時,因為和等於m的數必小於等於m,所以可以設定n=m.
演算法:
1.首先判斷,如果n>m,則n中大於m的數不可能參與組合,此時置n=m;
2.將最大的數n加入且n==m,則滿足條件,輸出;
3.將n分兩種情況求解:n沒有加入,取n=n-1,m=m,遞迴;
4.n加入,取n=n-1,m=m-n,遞迴。
5.結束。
代碼(來自網上):
intlength;/*輸入兩個整數n 和m,從數列,,.......n 中隨意取幾個數, 使其和等於m ,要求將其中所有的可能..*/voidfindCombination(int n,intm,bool *flag){ if(n < 1 || m < 1) return; if(n > m)//--減少遞迴次數---- n = m; if(n == m) { flag[n-1] = 1; for(inti=0;i<length;i++) { if(flag[i] == true) printf("%d\t",i+1); } printf("\n"); flag[n-1] = 0; } flag[n-1] = 1; findCombination(n-1,m-n,flag); flag[n-1] = 0; findCombination(n-1,m,flag);} intmain(){ int n, m; printf("n sum\n"); scanf("%d%d",&n,&m); length = n; bool *flag = (bool*)malloc(sizeof(bool)*length); findCombination(n,m,flag); free(flag); return 0;}
5.子集和問題(回溯法狀態空間樹)---問題4的推廣
求n個正整數構成的一個給定集合S={s1,s2,…sn}的子集,子集的和要等於一個給定的正整數d。例如對於S={1,2,6,8}和d=9來說,該問題有兩個解:{1,2,6}和{1,8}。當然,這個問題的某些執行個體是無解的。
思路:把集合元素按照升序排序,即s1<=s2<=...<=sn
我們可以按照二叉樹的形式來構造狀態空間樹,樹的根代表了起點,這時還沒有對給定的元素做任何決定。根的左右子節點分別代表在當前所求的子集中包含或者不包含S1,同樣的,從第一層的節點想左走表名包含S2,向右走則不包含,以此類推。因此,從根到樹的某個第i層節點的路徑,指出了該節點所代表的的子集中包含了前i個數字中的哪些數字。
我們把這些數位和s‘記錄在節點中。如果s‘等於d,我們就找到了問題的一個解。如果s‘不等於d,當下面兩個等式中的任何一個等式成立,我們就把該節點作為沒有希望的節點終止掉。
代碼(原創):
#include <iostream>#include <algorithm>using namespace std;int arr[] = {8,4,5,6,9,2,1}; // //int arr[] = {9,8,7,6,5,4,3,2,1};//int arr[] = {1,2,3,4,5,6,7,8,9,10};const int len = sizeof(arr)/sizeof(int);bool *flag = new bool[len];bool cmp(int x,int y);void GetSubSetSum(int n,int s);int Sum(int n);int main(){int s = 10;sort(arr,arr+len,cmp);//sort by descfor(int i = 0;i<len;i++)flag[i] = false;GetSubSetSum(len-1,s);return 0;}void GetSubSetSum(int n,int s){if(s == 0)//有解{for(int i = 0;i<len;i++){if(flag[i] == true){cout << arr[i] << " ";//flag[i] = false;}}cout << endl;}if(s<arr[n] || s>Sum(n))//s過小或者過大都無解return;//狀態空間樹 分支//包含arr[n]flag[n] = true;GetSubSetSum(n-1,s-arr[n]);//不包含arr[n]flag[n] = false;GetSubSetSum(n-1,s);}int Sum(int n){int s = 0;//for(int i = 0;i<n;i++)for(int i = 0;i<=n;i++)s += arr[i];return s;}bool cmp(int x,int y){return y < x;}
6.子數組的最大和
當我們加上一個正數時,和會增加;當我們加上一個負數時,和會減少。
如果當前得到的和是個負數,那麼這個和在接下來的累加中應該拋棄並重新清零;不然的話這個負數將會減少接下來的和;
當前和大於最大和,則重設最大和;
輸入有一類特殊情況需要特殊處理。當輸入數組中所有整數都是負數時,子數組和的最大值就是數組中的最大元素。
記錄重設當前和時的數組下標和最後一次重設最大和的數組下標,可以輸出該子數組的每一個元素。
//子數組的最大和[演算法] void MaxSum(int array[], unsigned int len){int beg = 0,last = 0;//開始求和 和 最後一次更新最大值的位置if(NULL == array || len <=0){return;}int curSum = 0, maxSum = 0;int i = 0;for(i=0; i<len; i++){curSum += array[i];// 累加if(curSum < 0){//-key2-- 當前和小於0,重設為0beg = i + 1;//curSum = 0;}if(curSum > maxSum){// -key1--當前和大於最大和,則重設最大和-- (3, 10, -4, 7, 2, -5,4,2)maxSum = curSum;last = i;//}}/*輸入有一類特殊情況需要特殊處理。當輸入數組中所有整數都是負數時,子數組和的最大值就是數組中的最大元素。*/if(maxSum == 0){// -key3--最大和依然為0,說明數組中所有元素都為負值maxSum = array[0];for(i=1; i<len; i++){if(array[i] > maxSum){maxSum = array[i];beg = i;last = i;}}}cout << "maxSum:" << maxSum << endl;cout << "sub arr is: " << endl;for(i = beg;i<=last;i++)cout << array[i] << " ";cout << endl;}
7.N個雞蛋放進M個籃子問題
有N個雞蛋和M個籃子,把雞蛋放到M個籃子裡,每個籃子都不可為空。另外,需要滿足:任意一個小於N的正整數,都能由某幾個籃子內蛋的數量相加的和得到。寫出程式,使得輸入一個(N,M),輸出所有可能的分配情況。
演算法問題分類---整數和問題系列總結