首页 > 代码库 > 算法问题分类---整数和问题系列总结
算法问题分类---整数和问题系列总结
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 desc for(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,重置为0 beg = 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),输出所有可能的分配情况。
算法问题分类---整数和问题系列总结