首页 > 代码库 > 算法问题分类---整数和问题系列总结

算法问题分类---整数和问题系列总结

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+n2S(我们需要的和)进行比较,>,则n2=a[--high];若<, n1=a[++low],若==,则使用mul保存乘积,还要保存两个数,因为可能不只一对数,它只要乘积小的!扫描直到low>=high终止!

 

3.判断一包含n个整数a[]中是否存在ijk满足a[i] + a[j] = a[k]的时间复杂度最小值是:

A.O(n^2)        B. O(n^2*logn)       C. O(n^3)   D. O(nlogn)

分析:先递增排序(Onlgn)),再固定每一个元素,按照求和为S的两个数字S的方法求解(n*O(n)=O(n^2)

求和问题总结(leetcode 2Sum, 3Sum, 4Sum, K Sum)

http://blog.csdn.net/doc_sgl/article/details/12462151

曾经在网上见了一个帖子讨论该问题Onlgn)的方法,可惜现在一直找不到了,大概是利用相反数之类的思想。

 

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

算法问题分类---整数和问题系列总结