1. 堆排序是一种不稳定的排序,时间复杂度是O(nlogn),最差也是O(nlogn),堆排序也可看成是一个“完全二叉树”,其中MinHeap经常被当作最小优先队列。堆排序的过程:每次弹出堆的堆顶元素,然后把堆的最右下叫元素交换,然后重新调整堆。
调整堆主要依据:找左/右子节点的最大的那个元素,然后交换,下沉父节点。
堆的插入和删除:
> 插入:放到最右下角,然后调整堆
> 删除:删除指定元素后,用最右下角的元素补充,然后调整堆
2. 给你5个球,每个球被抽到的可能性为30、50、20、40、10,设计一个随机算法,该算法的输出结果为本次执行的结果。
#include <stdlib.h> #include <time.h> #define M 5 int main() { int a[5] = {30,50,20,40,10}; int i,sum,ran; for(i = 0,sum = 0;i < M;i++) { sum += a[i]; } for(i = 0;i < M;i++) { srand(time(NULL)); ran = rand()%sum; if(ran <= a[i]) { printf("%c\t",'A'+i); } } printf("\n"); return 0; }
3. 五笔的编码范围是a ~ y的25个字母,从1位到4位的编码,如果我们把五笔的编码按字典序排序,形成一个数组如下: a, aa, aaa, aaaa, aaab, aaac, … …, b, ba, baa, baaa, baab, baac … …, yyyw, yyyx, yyyy。其中a的Index为0,aa的Index为1,aaa的Index为2,以此类推。
1)编写一个函数,输入是任意一个编码,比如baca,输出这个编码对应的Index;
2)编写一个函数,输入是任意一个Index,比如12345,输出这个Index对应的编码
1)计算出相邻2个同位数编码之间的距离,保存于base[4]中:
base[4] = 1, 即aaaa与aaab相隔;base[3]=base[4]*25+1,即aaa与aab相隔;base[2]=25*base[3]+1,即aa与ab相隔;base[1] = 25*base[2]+1,即a与b相隔
2)编码:给定一个字符编码,从高位向低位扫描。对第i位找出相同位数的,且前面i-1位相同,且第i位是a的编码之间的距离
例如baca:
第一步:找出b的位置,即与a之间的距离d1=('b'-'a')*base[1] + 1
第二步:找出ba的位置,即与ba之间的距离d2 = ('a'-'a')*base[2] + 1
第三步:找出bac的位置,即与baa之间的距离d3 = ('c'-'a')*base[3] + 1
第四步:找出baca的位置,即与baca之间的距离d4 = ('a'-'a')*base[4] + 1
即baca的Index = d1+d2+d3+d4-1
3)解码:给定一个索引值,依次去除base[i],得到与相同位数的,且前面i-1位相同,且第i位是a的编码之间的距离
4. 最小生成树
Prim算法:把定点集合V分为S(已属于最小生成树的顶点),V-S,然后找到pair (i,j),i属于S,j属于V-S,保证不出现环。时间复杂度是O(n*n)
Kruskal算法:依次挑选最短的边,加入最小生成树中,这里可能会出现环,因此要加一个判断是否会出现环的步骤。如何判断加入挑选的边是否形成环呢?因此一个额外数组v[]标记节点是否已经属于最小生成树,加入一条边的时候(i,j),如果v[i] = v[j] = 1,那么说明出现环。因此这里的时间复杂度是O(n*n + eloge)
5. 拓扑排序:用queue辅助实现,对于图G,挑选入度为0的节点加入到queue中,然后从queue头取一个元素,把以s为起始节点的边都删去,并且重新统计节点入度,并把入度为0的节点加入到queue中去。时间复杂度为O(n+e),其中统计节点入度为O(e),遍历节点O(n),对每个边做入度-1操作为O(e)
6. 关键路径(need revisit !)
其中节点活动(边)最早开始时间 = 活动最迟开始时间的节点成为关键节点,由关键节点组成的路径为关键路径。这里的重点是求每个节点的最早开始时间,最晚开始时间。其中,最早开始时间,由图的拓扑排序可以求得,最迟开始时间由图的逆拓扑排序可以求出。
其中节点最早开始时间计算方式如下:
对于v,其所向前依赖的节点s都以计算其最早开始时间,那么etv[v] = max{etv[s] + w(v,s)},取max保证前面的节点都已完成
对于v,其最迟开始时间ltv[v] = min{etv[s] - w(v,s)},s为所有依赖v后续节点的集合。
然后对于边:
如果活动ai由弧<j,k>表示,其持续时间记为dut(<j,k>),则有如下关系
e(i) = ve(j)
l(i) = vl(k) - dut(<j,k>)
那所有e(i)=l(i)的节点即为关键路径上的节点。
6. 最短路径,两者的时间复杂度均为O(n*n*n) (need revisit!)
单源最短路径:Dijstkra算法
每对节点的最短路径:Folyd,其主要思想是 A[i][j] = min{A[i][j], A[i][k] + A[k][j]}
7. 一堆数字,如何快速找出不重复的数字
用2-bitmap,每个数字用两位数字表示,如果是00 -> 01,如果是01 -> 10,如果是10 不变,最后输出 bitmap上编码为01的即为没有重复的数字。
8. 如果用循环数组q[0..m-1]表示队列时,该队列只有一个队头指针front,不设队尾指针rear,求这个队列中从队列投到队列尾的元素个数(包含队头、队尾)。
两种情况:
if rear>front,此时n=rear-front+1;
if rear<front,此时n=rear-front+1+m;此时rear-front是一个负数。
所以综上就是:(rear-front+1+m)%m。
9. 母函数在排列组合中的应用
1) 假设有1元,2元,5元,10元硬币,可以从这三种硬币中找出多少种总值为20的组合方式?比如20个1块钱是一种,而一个10元的加2个5元是一种
2) 面前有三种食物,填充值分别为一成饱的小面包、填充值为三成饱的烤鸡腿和填充值为十成饱的烤乳猪,请问能找到几种吃法恰好每次都能吃成十成饱?
母函数也叫生成函数,wiki 中对母函数有详细的描述,而本文所涉及的母函数则限定为普通的母函数。
具体来说,母函数的表现形式为多项式展开式,如:1+x+x2+x3 每一项的x前面的系数和x的幂级数都保存着我们需要的信息。
比如说给我一堆2块钱的硬币,我们能组合出0,2,4,6,8.....的组合,而系数表示组合的不同方式,幂级数代表总值。
这样无限多个2块钱硬币组合所代表的多项式为 1+x2+x4+x6+x8+..........
可能具体的例子会比较清晰吧:假设给你1元钱,2元钱,3元钱的硬币各一个;其代表的多项式展开如下:
(1+x)(1+x2)(1+x3) = (1+x+x2+x3)(1+x3) = 1+x+x2+2x3+x4+x5+x6
注意最后结果: 1+x+x2+2x3+x4+x5+x6 ,比如其中2x3表示为,总额为3的硬币组合有2种。
了解了母函数所表示的信息之后,我们就把原问题转换为了多项式的乘法了。
3)假设有x1个字母A, x2个字母B,..... x26个字母Z,假设字母A的价值为1,字母B的价值为2,... 字母Z的价值为26。那么,对于给定的字母,可以找到多少价值<=50的单词呢?单词的价值就是组成一个单词的所有字母的价值之和,比如,单词ACM的价值是1+3+14=18,单词HDU的价值是8+4+21=33。(组成的单词与排列顺序无关,比如ACM与CMA认为是同一个单词)
10. 最大重叠区间大小
对一个正整数n,如果n在数据文件中某行的两个正整数(假设为A和B)之间,即A<=n<=B或A>=n>=B,则n属于该行;如果n同时属于行i和j,则i和j有重叠区间;重叠区间的大小是同时属于行i和j的整数个数。例如,行(10 20)和(12 25)的重叠区间为[12 20],其大小为9;行(20 10)和(12 8)的重叠区间为[10 12],其大小为3。 输入数据:程序读入已被命名为input.txt的输入数据文本文件,该文件的行数在1到1,000,000之间,每行有用一个空格分隔的2个正整数,这2个正整数的大小次序随机,每个数都在1和2^32-1之间。 输出数据:在标准输出上打印出输入数据文件中最大重叠区间的大小,如果所有行都没有重叠区间,则输出0。
step1:对每行记录按左端节点从小到大排序
step2:顺序遍历,对于位置i,intv[i].left < intv[i-1].right,则区间相交。
a=min(intv[i].right,intv[i-1].right)
b=max(intv[i].right,intv[i-1].right)
step3:相交区间为a-intv[i].left+1(如果大于当前发现的最大重叠区间的话就将其替换);把b的值赋给intv[i].right,继续下一步的处理;
排序的时间复杂度为O(nlogn),处理的复杂度为O(n)。