笔试——编程&算法
2015-09-03 20:33
435 查看
1、子序列最大和
给定整数序列A1 A2….An,长度为n,其中整数可能为负数,现在要求A1~An的一个子序列Ai~Aj,使得Ai到Aj的和最大,并输出最大的和。int MAXseq(const int a[], int n) { int s1,s2,j; s1 = s2 = 0; for(j=0;j<n;j++) { s1 += a[j]; //s1记录累加和 if(s1 > s2) { s2 = s1; } else if(s1 < 0) { s1 = 0; } } return s2; }
2、求链表中间结点
最简单的想法:先遍历链表统计结点个数,然后结点数/2就是中间结点的位置,再遍历就可以指向中间结点如果只能遍历一次呢?
优化算法:设定两个指向第一个结点的指针,一个(p)一次走一个,一个(q)一次走两个。当走的快的到达链表尾部(q->next==NULL ||q->next->next==NULL)时,慢的就指向中间结点。当然要特别考虑结点个数为0 1 2的情况。
REF: /article/8510287.html
3、排序-折半插入排序
//折半插入排序 void Binary_insert_sort(node *a,int n) { int i,j,head,tail,mid; node tmp; for(i=1;i<n;i++) { if(a[i].a < a[i-1].a) { copy(&tmp,a[i]); head = 0; tail = i-1; /* mid的计算应该在tail/head改变之前计算 ** 避免当tail/head为负值的情况 mid = (head + tail)/2; while(tail >= head) { if(tmp.a < a[mid].a) { tail = mid -1; } else { head = mid +1; } mid = (head + tail)/2; } */ while(tail >= head) { mid = (head + tail)/2; //mid可能为负数 if(tmp.a < a[mid].a) { tail = mid -1; //tail可能为负数 } else { head = mid +1; } } //插入在**tail后**的位置(tail可能为负数) //for(j=i-1;j>tail;j--) 这种写法是错误的j>tail可能永远成立 for(j=i-1;j>=**tail+1**;j--) { copy(a+j+1,a[j]); } copy(a+j+1,tmp); } } }
4、二叉树-递归算法
求深度:int BiTreeDepth(const BiTree t) { int ld,rd; if(!t) return 0; //递归退出的地方。 ld = Depth(t->left); rd = Depth(t->right); return ld>rd?ld+1:rd+1; //树的深度为其左右子树的深度中最大者+1 }
5、计算1/0的位数,奇偶校验
奇偶校验:unsigned int v; // 待检测的数字 bool parity = false; //初始判断标记 int num_of_1 = 0; while (v) { parity = !parity; v = v & (v - 1); num_of_1 ++; }
v = v & (v - 1); 每执行一次,v中的1的个数减少一个。
REF: http://www.cnblogs.com/cpoint/p/3367375.html#top
6、周期串
如果一个字符串可以由某个长度为k的字符串重复多次得到,则该串以k为周期。例如,abcabcabcabc以3为周期(注意,它也以6和12为周期)。输入一个长度不超过80的串,输出它的最小周期。样例输入:helpshelpshelpshelps
样例输出:5
#include <stdio.h> #include <stdlib.h> #include <string.h> void cycle_length(char *a,int length) { int i,j; int flag; for (i=1;i<length;i++) { if (length%i == 0) //如果i为最小周期,那么字符串长度必定是i的整数倍 { flag = 1; for (j=i;j<length;j++) //判断数组是否已i为周期 { if (a[j-i] != a[j]) { flag = 0; } else break; } if (flag) { printf("%d \n",i); //输出对应的周期 break; //如果flag不为0,那么退出循环 } } } } //测试函数 void main() { char *a="abcabcabc"; cycle_length(a,strlen(a)); system("pause"); }
7、走格子问题 非递归/递归实现
程序员面试宝典 P88解法1:
我们可以把棋盘的左下角看做二维坐标的原点(0,0),把棋盘的右上角看做二维坐标(M,N)(坐标系的单位长度为小方格的变长)
用f(i,j)表示移动到坐标f(i,j)的走法总数,其中0=
int process(int m, int n) { if (m == 0 && n == 0) return 0; if (m==0 || n==0) return 1; return process(m, n - 1) + process(m - 1, n); }
int processNew(int m,int n){ int **Q=new int*[m+1]; for(int i=0; i<=m; ++i){ Q[i]=new int[n+1](); } //初始化 Q[0][0]=0; for(int j=1; j<=n; ++j) Q[0][j]=1; for(int i=1; i<=m; ++i) Q[i][0]=1; //迭代计算 for(int i=1; i<=m; ++i){ for(int j=1; j<=n; ++j){ Q[i][j]=Q[i-1][j]+Q[i][j-1]; } } int res=Q[m] ; delete [] Q; return res; }
8、反转二进制位
C和指针 P89例如在32位机器上,25为:
0000 0000 0000 0000 0000 0000 0001 1001
反转后为:
1001 1000 0000 0000 0000 0000 0000 0000
并且不能依赖于机器的整形长度。
unsigned int reverse_bits(unsigned int value) { unsigned int i; unsigned int answer; //返回的反转后的结果整形值。 answer = 0; for(i = 1;i != 0;i <<= 1) //当机器为N位时,此for循环将执行N次。 { answer << 1; //为下一个位留下空间,即将旧值移向高位。 if(value & 1 == 1) //若value的最低位,即当前处理位为1时 answer |= 1; //answer的最低位同样需要置1。 value >> 1; //当前位处理完成,移走之,进行下一位的处理。 } return answer; }
9、打印美元数据——字符串处理
C和指针 P191输入 0 1 12 1234 123456789 分别打印出:0.000.00 0.01 0.120.12 12.34 $1,234,567.89
void dollars(char *dest, char const *src) { int len = strlen(src); int i; //处理符号 *dest++ = '$'; //处理小数点前面的数值 if(len > 2) { for(i=len-2;i>0;) { *dest++ = *src++; if(--i && i%3 == 0) //只有当能被3整除时才需要添加逗号 *dest++ = ','; } } else *dest++ = '0'; *dest++ = '.'; //处理小数点后面的数值 *dest++ = len < 2 ? '0':*src++; //当len=1时,添加0 当len=2时,添加*src *dest++ = len < 1 ? '0':*src; //当len=1时,添加*src 当len=2时,添加*src *dest = '\0'; }
10、输出后续遍历二叉树
《算法竞赛入门经典》 P106题目:输入二叉树的先序遍历和中序遍历,输出其后续遍历。
//n为节点数,s1先序,s2中序,s后序 void build(int n, char *s1, char *s2, char *s) { if(n <= 0) return; int root_idx = strchr(s2,s1[0]) - s2; //根节点在中序遍历的位置 build(root_idx, s1+1, s2+1, s); build(n - root_idx -1, s1+root_idx+1, s2+root_idx+1, s+root_idx); s[n-1] = s1[0]; //根节点放到最后 }
通过先序中序构造二叉树:
一个先序遍历序列和一个中序遍历序列可以确定一颗唯一的二叉树。
根据先序遍历的特点, 知先序序列(PreSequence)的首个元素(PreSequence[0])为二叉树的根(root), 然后在中序序列(InSequence)中查找此根(root), 根据中序遍历特点, 知在查找到的根(root) 前边的序列为根的左子树的中序遍历序列, 后边的序列为根的右子树的中序遍历序列。 设在中序遍历序列(InSequence)根前边有left个元素. 则在先序序列(PreSequence)中, 紧跟着根(root)的left个元素序列(即PreSequence[1…left]) 为根的左子树的先序遍历序列, 在后边的为根的右子树的先序遍历序列.而构造左子树问题其实跟构造整个二叉树问题一样,只是此时先序序列为PreSequence[1…left]), 中序序列为InSequence[0…left-1], 分别为原序列的子串, 构造右子树同样, 显然可以用递归方法解决。
自己编写的有错误的代码:
//DBACEGF ABCDEFG 先序中序建立二叉树 void build(BiTree **t, int n, char *pre, char *in) { BiTree * q = *t; if(n <= 0) return; q = (BiTree *)malloc(sizeof(BiTree)); if(!q) return; q->s = pre[0]; int root_idx; root_idx = strchr(in, pre[0]) - in; build(&(q->left), root_idx, pre+1, in); build(&(q->right), n-root_idx-1, pre+root_idx+1, in+root_idx+1); }
当用这段代码构造二叉树后,再遍历二叉树时会出现段错误,因为构造的二叉树没有分配内存。
这段代码错误的关键在于,先用q指向了*t,但是当分配内存后,q指向了另外一块内存,跟 *t或者说t没什么关系了,因此调用此函数后, *t所指向的二叉树根本就是空的。
修改为:
BiTree * q; q = (BiTree *)malloc(sizeof(BiTree)); if(!q) return; *t = q;//用q指向*t,或者不用定义q,直接使用*t处理。
11、二叉树中序遍历非递归算法
《数据结构算法解析》 P141 142算法一:
void inorder_traverse(BiTree *t) { BiTree stack[Q_LEN],*p; int top,btn; top = btn = 0; while(t || top != btn) //二叉树不空或者栈不空时 { if(t) //二叉树不空,则根指针进栈,遍历左子树 { stack[top++] = t; t = t->left; } else //二叉树为空,访问根节点遍历右子树 { p = stack[--top]; printf("%d ", p->index); t = t->right; } } }
算法二:
void inorder_traverse2(BiTree *t) { BiTree stack[Q_LEN],*p; int top,btn; top = btn = 0; stack[top++] = t; //先入栈根指针 while(top != btn) //当栈不空时 { while((p = stack[top-1])) //取栈顶元素且自己和其左子树不空 stack[top++] = p->left; //向左走到尽头,入栈左孩子指针 top--; //弹出最后入栈的空指针。 if(top != btn) { p = stack[--top]; //出栈并访问 visit(p); stack[top++] = p->right; //入栈右孩子指针 } } }
以下为对算法二的改变:
不对任何空的指针进行入栈的操作。
中序遍历的操作是:左中右。
我们先把所有左孩子入栈。
然后在出栈一个节点,此时的节点就是最左边的孩子,访问之,而在它之前的栈中元素就是它的父节点,也可以访问之,此时左中都访问完毕;
再入栈右孩子,如此重复对右子树的操作。
void inorder_traverse2(BiTree *t) { BiTree stack[Q_LEN],*p; int top,btn; top = btn = 0; stack[top++] = t; //先入栈根指针 while(top != btn) //当栈不空时 { while((p = stack[top-1]) && p->left != NULL) //取栈顶元素且自己和其左子树不空 stack[top++] = p->left; //向左走到尽头,入栈左孩子指针 while(top != btn) { p = stack[--top]; //出栈并访问 visit(p); if(p->right) { stack[top++] = p->right; //入栈右孩子指针 break; } } } }
12、分数拆分
《算法竞赛入门经典》 P115题目:
输入正整数k,找到所有的正整数x>=y,使得 1/k = 1/x + 1/y。
耗时大的代码:
void fraction() { int k; scanf("%d",&k); int x,y,i,j; x=y=1; j = 2*k; while(x >= y) { for(y=1;y<=x&&y<=j;y++) { i=x*y; if(i%k == 0 && i/k == x+y) { printf("1/%d = 1/%d + 1/%d\n",k,x,y); } } x++; } }
此时x的循环非常大,而y<=2*k的结论是根据x>=y得来的,1/x <= 1/y => 1/x + 1/y <= 2/y。但是,我们可以根据k,y计算出x而不需要循环测试x。
void fraction() { int k; scanf("%d",&k); int x,y,i,j; x=1; for(y=1;y<=2*k;y++) { j = k*y; i = y-k; if(i && j%i == 0) //必须判断分母不为零。 { x = j/i; if(x>=y) { printf("1/%d = 1/%d + 1/%d\n",k,x,y); } } } }
13、十进制转n进制
//十进制a转换成n(2~10)进制数 void sys_trans(int a, int n, int *p, int *len) { while(a) { *p++ = a%n; a /= n; tmp ++; (*len) ++; //原来错误的写法为*len++,此时是取len所指的内容,然后丢弃,len往后移一位。 } }
14、 杨氏矩阵算法
杨氏矩阵:m行n列杨氏矩阵类似下面这个样子,行从左到右越来越小,列从上到下越来越大,让你找一个元素x,如何在O(n)时间内找到,n表示行列个数总和,如下,找9
10 6 4 2 0
12 7 6 3 1
13 8 7 5 2
这种情况下,可以从左上角开始比较,如果比x大,那么第一列都得去除,变成
6 4 2 0
7 6 3 1
8 7 5 2
接着从左上角比较,如果小,就把第一行去除,
7 6 3 1
8 7 5 2
再比较,比9小,去除第一行
8 7 5 2
再比较去除8,去除7,5,2
这样的话每次去除最多一行或者一列,时间复杂性是O(m+n)。
17、zigzag数组
《程序员面试宝典》 P92ZigZag数组就是形如下图的,依次沿对角线增加->减小交替变换的数组
0 1 5 6 14 15 27 28
2 4 7 13 16 26 29 42
3 8 12 17 25 30 41 43
9 11 18 24 31 40 44 53
10 19 23 32 39 45 52 54
20 22 33 38 46 51 55 60
21 34 37 47 50 56 59 61
35 36 48 49 57 58 62 63
18、螺旋队列问题
《程序员面试宝典》 P95设1的坐标是(0,0),x方向向右为正,y方向向下为正,例如,7的坐标为(-1,-1),2的坐标为(1,0)。编程实现输入任意一点坐标(x,y),输出所对应的数字!
关键点:
1、第t层从(2t-1)²+1开始,1为第零层。
2、给定坐标(x,y),可求得其所在的层次,t=max(|x|,|y|)
参考:/article/8060430.html
19、蛇形矩阵
《程序员面试宝典》 P97【思路】:领用前面方向的数是否为0来判断是否达到尽头。
void p_fun(int n) { int i,j,tmp = 1,n2 = n*n; memset(za,0,100*100*sizeof(int)); i=j=0; za[i][j] = tmp++; while(tmp <= n2) { while(j+1<n && !za[i][j+1]) za[i][++j] = tmp++; //j+1<n保证了za[i][j+1]没有越界错误,不会错误的去判断za[i] 位置的数。 while(i+1<n && !za[i+1][j]) za[++i][j] = tmp++; while(j>0 && !za[i][j-1]) za[i][--j] = tmp++; while(i>0 && !za[i-1][j]) za[--i][j] = tmp++; } for(i=0;i<n;i++) { for(j=0;j<n;j++) printf("%d ",za[i][j]); printf("\n"); } }
20、并查集算法
题目:ACM 畅通工程 http://acm.nyist.net/JudgeOnline/problem.php?pid=608参考:/article/2376314.html
#define MAX_TOWN_NUM 1000 int pre[MAX_TOWN_NUM]; //找根节点(找自己的老大) int find(int x) { if(pre[x] == x) //老大是自己 return x; else { find(pre[x]); } } //将两点连到一个集合中 int join(int x, int y) { int pre_x,pre_y; pre_x = find(x); pre_y = find(y); if(pre_x != pre_y) { pre[pre_x] = pre_y; //把y的老大变成x的老大的老大 } } //初始化并查集,让每个人都是自己一个人的集合,即每个人的老大是自己 void init(int n) { int i; for(i=1; i<=n; ++i) pre[i] = i; } int main() { int town_cnt,road_cnt,count; int x,y,i,j; int *tmp; scanf("%d",&town_cnt); while(town_cnt > 0) { init(town_cnt); tmp = (int*)malloc(sizeof(int)*(town_cnt+1)); if(!tmp) continue; memset(tmp,0,town_cnt); count = 0; scanf("%d",&road_cnt); while(road_cnt--) { scanf("%d%d",&x,&y); join(x,y); } //加入人员进来后,进行【路径压缩】 for(i=1;i<=town_cnt;++i) { j = find(pre[i]); //找到i的老大 if(tmp[j] == 0) { count++; tmp[j] = 1; } } scanf("%d",&town_cnt); } }
21、itoa实现算法 integer to alphanumeric
char* itoa(int value, char* string, int radix);将任意类型的数字转换为字符串 int radix 转换进制数,如2,8,10,16 进制等
int atoi(const char *nptr);
把字符串转换成整型数
【注意点】num是负数时,需要特殊处理负号。然后再进行进制转换,还需要逆置转换后的内容。
char* itoa(int num,char*str,int radix) { /*索引表*/ char index[]="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; //可求10进制向1~36进制的转换 unsigned unum; int i=0,j,k; /*确定unum的值*/ if(radix==10 && num<0)/*十进制负数*/ { unum=(unsigned)-num; str[i++]='-'; } else /*其他情况*/ unum=(unsigned)num; /*转换*/ do { str[i++] = index[unum%(unsigned)radix]; //进制转换 unum /= radix; }while(unum); str[i]='\0'; /*逆序*/ if(str[0] == '-') k=1;/*十进制负数*/ else k=0; char temp; for(j=k;j<=(i-1)/2;j++) { temp=str[j]; str[j]=str[i-1+k-j]; str[i-1+k-j]=temp; } return str; }
22、卡特兰数
公式:令h(0)=1,h(1)=1,catalan数满足递推式:
h(n) = h(0)* h(n-1)+h(1)*h(n-2) + … + h(n-1)h(0) (n>=2)
h(n) = C(2n,n)/(n+1) (n=0,1,2,…)
应用:
出栈次序
1、一个栈(无穷大)的进栈序列为1,2,3,…,n,有多少个不同的出栈序列?
h(n)种。
2、买票找零:有2n个人排成一行进入剧场。入场费5元。其中只有n个人有一张5元钞票,另外n人只有10元钞票,剧院无其它钞票,问有多少中方法使得只要有10元的人买票,售票处就有5元的钞票找零?(将持5元者到达视作将5元入栈,持10元者到达视作使栈中某5元出栈)
3、给定N个节点,能构成多少种不同的二叉树?h(n)
实现
void catalan() //求卡特兰数 { int i, j, len, carry, temp; a[1][0] = b[1] = 1; len = 1; for(i = 2; i <= 100; i++) { for(j = 0; j < len; j++) //乘法 a[i][j] = a[i-1][j]*(4*(i-1)+2); carry = 0; for(j = 0; j < len; j++) //处理相乘结果 { temp = a[i][j] + carry; a[i][j] = temp % 10; carry = temp / 10; } while(carry) //进位处理 { a[i][len++] = carry % 10; carry /= 10; } carry = 0; for(j = len-1; j >= 0; j--) //除法 { temp = carry*10 + a[i][j]; a[i][j] = temp/(i+1); carry = temp%(i+1); } while(!a[i][len-1]) //高位零处理 len --; b[i] = len; } }
23、扔玻璃球求最高楼层
题目:某幢大楼有100层。你手里有两颗一模一样的玻璃珠。当你拿着玻璃珠在某一层往下扔的时候,一定会有两个结果,玻璃珠碎了或者没碎。这幢大楼有个临界楼层。低于它的楼层,往下扔玻璃珠,玻璃珠不会碎,等于或高于它的楼层,扔下玻璃珠,玻璃珠一定会碎。玻璃珠碎了就不能再扔。现在让你设计一种方式,使得在该方式下,最坏的情况扔的次数比其他任何方式最坏的次数都少。也就是设计一种最有效的方式。
首先,为了保存下一颗玻璃珠自己玩,就采用最笨的办法吧:从第一层开始试,每次增加一层,当哪一层扔下玻璃珠后碎掉了,也就知道了。不过最坏的情况扔的次数可能为100。
当然,为了这一颗玻璃珠代价也高了点,还是采取另外一种办法吧。随便挑一层,假如为N层,扔下去后,如果碎了,那就只能从第一层开始试了,最坏的情况可能为N。假如没碎,就一次增加一层继续扔吧,这时最坏的情况为100-N。也就是说,采用这种办法,最坏的情况为max{N, 100-N+1}。之所以要加一,是因为第一次是从第N层开始扔。
不过还是觉得不够好,运气好的话,挑到的N可能刚好是临界楼层,运气不好的话,要扔的次数还是很多。不过回过头看看第二种方式,有没有什么发现。假如没摔的话,不如不要一次增加一层继续扔吧,而是采取另外一种方式:把问题转换为100-N,在这里面找临界楼层,这样不就把问题转换成用递归的方式来解决吗?看下面:
假如结果都保存在F[101]这个数组里面,那么:
F
=100-N,
F[100]=min(max(1,1+F[N-1]),max(2,1+F[N-2]),……,max(N-1,1+F[1]));
看出来了没有,其实最终就是利用动态规划来解决这个问题。
下面是自己随便写的C++代码:
#include<iostream> using namespace std; int dp[101] = { 0 }; void solve() { int i , j , k; for(i = 2 ; i < 101 ; ++i) { dp[i] = i; for(j = 1 ; j < i ; ++j) { k = (j>=(1 + dp[i-j])) ? j : (1 + dp[i-j]); if(dp[i] > k) dp[i] = k; } } } int main(void) { dp[0] = 0 , dp[1] = 1; solve(); printf("%d\n",dp[100]); return 0; }
输出结果为14。也就是说,最好的方式只要试14次就能够得出结果了。
答案是先从14楼开始抛第一次;如果没碎,再从27楼抛第二次;如果还没碎,再从39楼抛第三次;如果还没碎,再从50楼抛第四次;如此,每次间隔的楼层少一层。这样,任何一次抛棋子碎时,都能确保最多抛14次可以找出临界楼层。
证明如下:
1、第一次抛棋子的楼层:最优的选择必然是间隔最大的楼层。比如,第一次如果在m层抛下棋子,以后再抛棋子时两次楼层的间隔必然不大于m层(大家可以自己用反证法简单证明)
2、从第二次抛棋子的间隔楼层最优的选择必然比第一次间隔少一层,第三次的楼层间隔比第二次间隔少一层,如此,以后每次抛棋子楼层间隔比上一次间隔少一层。(大家不妨自己证明一下)
3、所以,设n是第一次抛棋子的最佳楼层,则n即为满足下列不等式的最小自然数:
不等式如下: 1+2+3+…+(n-1)+n >= 100
由上式可得出n=14
即最优的策略是先从第14层抛下,最多抛14次肯定能找出临界楼层。
REF: /article/5085972.html
24、走台阶问题
题目:一个楼梯有50个台阶,每一步可以走一个台阶,也可以走两个台阶,请问走完这个楼梯共有多少种方法?REF:/article/1791115.html
相关文章推荐
- 类再生
- HDU 4919(Exclusive or-java的HashMap类)
- 牢骚 - 你代码写得丑,又不肯用好一点的IDE,这让我很为难啊。
- <转>四个重要属性——Action、Data、Category、Extras
- delphi数据库的备份及还原
- 【socket】有关socket的编程<一>
- 隐藏实施过程
- Spring data 规范
- java 常用方法
- 三、NoteEditor.java文件学习笔记
- 【英语】Bingo口语笔记(63) - 一个单词的多种发音
- Delphi考虑sql注入 QuotedStr
- 【英语】Bingo口语笔记(62) - 生气道歉场景的表达
- 初始化和清除
- Java中多线程重复启动
- Ruby on Rails Guides(一)
- 20150902 Java学习笔记-构造方法,关键字,封装性
- C++模板元编程 - 3 逻辑结构,递归,一点列表的零碎,一点SFINAE
- 如何写memset函数
- 【Socket】MFC之编写聊天通信代码