算法题集-2

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 。

 

clip_image002

趣题:从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/

转载于:https://www.cnblogs.com/xkfz007/archive/2012/11/20/2779186.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值