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.youkuaiyun.com/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),输出所有可能的分配情况。