K阶锦标赛算法
一个赛马场有100匹马,5条赛道,至少要比赛多少场才能选出跑得最快的10匹马?
步骤:
1. 将100匹马分成20次比赛,每次5匹马,并保存每次比赛的结果(即第1名到第5名的顺序)
2. 从胜利的20匹马分成4次比赛,每次5匹马,并保存每次比赛的结果(即第1名到第5名的顺序)
3. 对最后的4匹马进行比赛,并保存每次比赛的结果(即第1名到第4名的顺序)
即通过以上3个步骤,其实已经建立一个4层的树结构。并且已经得到第1名A
4. 在4层树结构中,挑出与A比过赛的且失败的,且是第2名的马(不超过5位),挑选出第2名B
在此过程中,可以想象将A删除,并在比较过程中恢复树结构
依次轮推,直至找出前10名
总的比较次数为:
选出第一名:20+4+1
选出第二名:1
选出第三名:1
...
选出第十名:1
总次数:34次
问题描述:一个骰子有6面,概率均匀,我有7件事要随机均匀选择,用最少的扔掷次数如何仍?
问题扩展:如果我有4件事,5件事,8件事,9件事,10件事 …… n件事要选择,那么我如何用最少的扔掷次数来做到均匀分布?
思路:
注意问题中的关键字:(1) 最少扔掷的次数 (2) 要求概率均匀分布
用容易理解的方式再将问题描述一遍:我们周末决定去聚餐,此时有6家备选饭店供我们选择,为了公平,用骰子扔掷一次即可以随机选出一家饭店。
但是,此时如果有7家备选饭店,同样要做到公平,我们如何用6个面的骰子选择去哪家饭店呢?
方法:
用骰子扔2次,可以得到 [7, 42] 区间中的一个数(视为6进制),假设扔掷的数表示为m,如果 m=7 则舍弃,否则将 m mod 7 得到的数即为最终结果。
PS: m=7 时,会使得 0 的概率变大,所以要舍弃。
将上述表述再换种方式理解,用骰子扔2次,可以得到 [7, 42] 区间中的一个数,即此时得到了 36 个随机数,对应区间 [1, 36]。因此,假设扔掷的数表示为m,如果 m=36 则舍弃,否则将 m mod 7 得到的数即为最终结果。同理,当 m=36 时,会使得 1 的概率变大,所以要舍弃。
结论:
(1) 灵活使用N进制的思想,虽然生活中多用10进制。本题使用的是6进制的思想,即表示为: result = Y * 6 + X,其中,X 表示第一次扔掷的点数,Y 表示第二次扔掷的点数。
(2) 使用求和的方法不满足概率均匀分布,求和的结果属于正态分布,中间值的概率最大,出现最大值和最小值的概率最小。
设计一个算法找到数组中两个元素相加等于指定数的所有组合
思路:将数组排序,然后用两个指向数组的指针,一个从前往后,一个从后往前,记为first和last,如果 fist + last < sum 则将fist向前移动,如果fist + last > sum,则last向后移动。
#if 1 #include <vector> #include <iostream> #include <algorithm> #include <iterator> #include <cstdlib> using namespace std; int main(){ vector<int> iv; int N=20; // srand(time(0)); for(int i=0;i<N;i++){ int val=rand()%1000; if(val&0x1) val=-val; iv.push_back(val); } int s=109;//rand()%10000; copy(iv.begin(),iv.end(),ostream_iterator<int>(cout," ")); cout<<endl; sort(iv.begin(),iv.end()); copy(iv.begin(),iv.end(),ostream_iterator<int>(cout," ")); cout<<endl; vector<int>::const_iterator ite1=iv.begin(), ite2=iv.end()-1; while(ite1!=ite2){ int sum=*ite1+*ite2; if(sum>s) --ite2; else if(sum<s) ++ite1; else { cout<<*ite1<<' '<<*ite2<<endl; ++ite1; --ite2; } } } #endif
18. 卡特兰数
http://baike.baidu.com/view/2499752.htm
19. LIS最长递增子序列
请给出一个O(nlogn)的算法,使之能够找出一个n个数的序列中最长的单调递增子序列。
这是算法导论中的一道课后题。
解法一:利用求最长公共子序列的思想,将n个数的序列A先排序形成一个有序的序列B,然后利用动态规划的思想求A与B的最长公共子序列,得到的最长公共子序列就是所求的解。但是我们知道最长公共子序列的解法是O(n2),所以不满足题目要求,此法不通。
解法二:对包含n个数的序列A,我们使用一个数组C,其中C[i]记录长度为i的所有单调递增子序列的最小的元素(可能说的不清楚,举例说明比如A有3个长为2的单调递增子序列1,2;3,4;5,6;那么C[2]的值就是2,记录的是最小的那个子序列的最后一个元素)。具体的遍历过程是对于一个元素A[i],通过二分查找C(因为C是有序数组),获取A[i]的位置flag,比较A[i]与C[flag]的大小,如果小于C[falg]用A[i]替换C[flag],如果大于用A[i]替换C[flag+1]。这样遍历一遍就能得到数组A的最长的公共子序列的长度,但是要获得最长公共子序列的组成,需要利用第一次遍历获得长度,再遍历一次,在给定的长度时将C中的元素拷贝出来,防止后边可能进行的破坏。虽然需要遍历两边但是满足时间复杂度的要求,大家有更好的方法欢迎讨论。
问题描述:两个数组a[N],b[N],其中A[N]的各个元素值已知,现给b[i]赋值,b[i] = a[0]*a[1]*a[2]...*a[N-1]/a[i];
要求:
1.不准用除法运算
2.除了循环计数值,a[N],b[N]外,不准再用其他任何变量(包括局部变量,全局变量等)
3.满足时间复杂度O(n),空间复杂度O(1)
#include <iostream> #include <cstdlib> using namespace std; const int N=10; int main1(int* a) { int i; int b[N]; b[0] = 1; for(i = 1; i < N; i++) { b[0] *= a[i - 1]; b[i] = b[0]; } b[0] = 1; for(i = N-2; i > 0; i--) { b[0] *= a[i + 1]; b[i] *= b[0]; } b[0] *= a[1]; for(i = 0; i < N; i++) { cout << b[i] << " "; } cout << endl; return 0; } int main2(int *a){ int i; int b[N]; b[N-1]=1; for(int i=1;i<N-1;i++){ b[N-1]*=a[i-1]; b[i]=b[N-1]; } b[N-1]*=a[N-2]; b[0]=1; for(int i=N-2;i>0;i--){ b[0]*=a[i+1]; b[i]*=b[0]; } b[0]*=a[1]; for(i = 0; i < N; i++) { cout << b[i] << " "; } cout << endl; } int main(){ srand(time(0)); int a[N]; for(int i=0;i<N;i++) { int val; while((val=rand()%10)==0); a[i]=val; } main1(a); main2(a); } 腾讯笔试题:计算下面函数的结果: static int ack(int m,int n){ if(m==0){ return n+1; } else if(n==0){ return ack(m-1,1); } else{ return ack(m-1,ack(m,n-1)); } }
腾讯面试题:1-20的两个数把和告诉A,积告诉B,A说不知道是多少,B也说不知道,这时A说我知道了,B接着说我也知道了,问这两个数是多少?
设和为S,积为M。
首先,A:我不知道。
说明:S可以分解成多个组合,而2=1+1,3=1+2,40=20+20,39=19+20,只有一种分解方式,因此S应属于[4,38]集合。
其次,B:我也不知道。
说明:M也可以分解成多个组合,因此M不是质数。
再者,A:我现在知道了。
说明:S分解方式中只有一个相乘之后是合数,其他分解方式相乘之后都是质数。这样,A才能根据B说不知道,而排出所有相乘是质数(M是质数,分解方式只有一种:1*质数)的可能,剩下的一个相乘之后是合数的组合就是A所得到的解。
而相乘之后是质数的:只有1*质数 = 质数!
1-20的所有质数:T = {2, 3, 5, 7, 11, 13, 17, 19}。
设x为T中的任意一个质数。那么,S的可能取值集合:{2+1, 3+1, 5+1, 7+1, 11+1, 13+1, 17+1, 19+1},即:SS = {3, 4, 6, 8, 12, 14, 18, 20}
S= 3时:3不在【4,38】集合,排除;
S= 4时:4=2+2=1+3,(2,2)相乘为4(非质数,满足条件),(1,3)相乘为3(质数,排除);
S= 6时:6=1+5=2+4=3+3,相乘分别为5,8,9,出现两个合数,排除;
其他值都是存在多个合数分解的情况,因此均排除了。
因此,A得到的解是2和2.
最后,B:我也知道了。
说明:B根据自己已知的M值,站在A的立场思考,能够获得M=4的结果,现在验证如下:
M=4=2*2=1*4,相加结果为4,5.而5不在SS集合之中,因此结果为2和2.
因此,最终答案为2和2.
以上给出的分析是假设这两个数是可以相同的。
如果认为这两个数不同,那又应该是哪两个数呢?
还是按照上面的步骤来进行分析:
首先,A:我不知道。
说明:S有多个分解方式。S属于【5,37】.
其次,B:我不知道。
说明:M有多种分解方式。
再者,A:我知道这两个数了。
说明:
S分解方式中只有一个相乘之后是合数,其他分解方式相乘之后的积仅有一种分解方式!这样,A才能根据B说不知道,而排出所有相乘是质数(M是质数,分解方式只有一种:1*质数)的可能,剩下的一个相乘之后是合数的组合就是A所得到的解。
那么,S的可能取值集合:{3,4,5,......,37}
S= 3时:3不在【5,38】集合,排除;
S= 4时:4=1+3,只有一种分解方式,排除;
S=5时:5=1+4=2+3,相乘分别为4,8,4=1*4仅有一种分解方式排除,8=1*8=2*4满足,得到一个解。
S= 6时:6=1+5=2+4,相乘分别为5,8,显然也满足。
其他值都是存在多个合数分解的情况,因此均排除了。
因此,解为2和3 或 2和4
最后,B:我也知道了。
说明:
B站在A立场得知结果。验证如下:
如果为2和3,则积为6,和为5。此时,5=1+4=2+3,4仅有一种分解方式,A能够确定为2和3;6=1*6=2*3,相加为7,5,此时7=1+6=2+5=3+4,相乘后为6,10,12,无法确定唯一解,舍掉1,6的解;而5=1+4=2+3,相乘后为4,6,舍掉4,有解2和3.
如果为2和4,则积为8,和为6.此时,6=1+5=2+4,5仅有一种分解方式,A能够确定为2和4. 8=1*8=2*4,相加为9,6,此时9=1+8=2+7=3+6=4+5,无法确定唯一解,舍掉1和8的解;而6=1+5=2+4,相乘后为5,6,舍掉5,有解2和4.
因此,最终解为2和3 或 2和4 。
趣题:从1到4000中各位数字之和能被4整除的有多少个?
一个小学奥数老师给我讲了一道小学奥数题,这是他在上课时遇到的:从 1 到 4000 中,各位数字之和能被 4 整除的有多少个?
注意,问题可能没有你想的那么简单,满足要求的数分布得并没有那么规则。 1 、 2 、 3 、 4 里有一个满足要求的数, 5 、 6 、 7 、 8 里也有一个满足要求的数,但是 9 、 10 、 11 、 12 里就没有了。
尽管如此,这个问题仍然有一个秒杀解。你能多快想到?
答案就是 1000 。首先, 0 和 4000 都是满足要求的数,因而我们不去看 1 到 4000 中有多少个满足要求的数,转而去看 0 到 3999 中有多少个满足要求的数,这对答案不会有影响。注意到,如果固定了末三位,比如说 618 ,那么在 0618 、 1618 、 2618 、 3618 这四个数中,有且仅有一个数满足,其各位数字之和能被 4 整除。考虑从 000 到 999 这 1000 个可能的末三位组合,每一个组合都唯一地对应了一个满足要求的四位数,因此问题的答案就是 1000 。
真正有趣的事情在后面呢。一个小朋友举手说:“老师,我明白了,按照这个道理,从 1 到 3000 里各位数字之和能被 3 整除的数也是 1000 个。”另一个小朋友说:“废话,各位数字之和能被 3 整除就表明整个数能被 3 整除,在 1 到 3000 里这样的数当然有 1000 个嘛!”全班哄堂大笑。
转自:http://www.matrix67.com/blog/archives/4644
题目:10G 个整数,乱序排列,要求找出中位数。内存限制为 2G。
解答:
拿到此题目首先考虑的是内存的限制,因而无法用快速排序或是部分排序。若是求的是最大值或最小值,或是K小的值(k<2G)则可以采用堆排序O(NlogK)。但现在是求中位数即排在第5G和5G+1的数
算法思路分析:假设是无符号整数,
第一步: 借鉴桶排序的思路,因为整数为32位,我们先按高16位2^16=64K进行计数,即分成64K段,这样需要的计数数组大小为2^16,若数组类型为int型,存在缺点,若10G的数都是相同,这样数组存的计数最大为2^32=4G,就会出现溢出,所以数组类型采用long long8字节型。占用内存为2^16*8B=518KB。而内存给了2G,可见利用得过少,表明还有很大的改进空间。 改进:分成2G/8B=2^28=256M段,这样段越多,第二步扫描分析的数据就越少。
long long Counter[1 < <28];//256M桶 unsigned int x; memset(Counter,0,sizeof(Counter)); foreachnumber(x) { Counter[x>>4]++; //高28位 } long long sum=0; for(i=0;i <1 < <28;i++) { sum+=Counter[i]; if(sum>=5LL < <30)break;//找到中位数所在的段 } sum-=5LL < <30; sum=Counter[i]-sum;//为达到5G,中位数所在的段需要的个数
第二步:前步已把10G数据按高28位分到了256M桶中,且已经找到中位数在哪一段,只要把此段按低4位分到16个段中,即可以找到
int segment=i; memset(Counter,0,sizeof(Counter)); foreachnumber(x) { if((x>>16)==segment) { Counter[x&(~((-1) < <16))]++; //低4位。 -1的8位二进制表示为11111111 } } long long lsum=0; for(i=0;i <1 < <4;i++) { lsum+=Counter[i]; if(lsum>=sum)break; } int keynum = (segment<<4)|(i);
总共只要读两遍整数,对每个整数也只是常数时间的操作,总体来说是线性时间
若是有符号的整数,只需改变映射即可。
参考:http://grachel1986.blog.163.com/blog/static/5660389320108271179910/