几个面试算法题,附源码

来源:互联网 发布:网络通信加密软件 编辑:程序博客网 时间:2024/04/26 05:56

昨天下午面试了一家,最后有道算法题,当时没想起来,就拍了张照,回来慢慢想,也算没白去。


原题 :  有N个人围成一圈,顺序排号。从第一个人开始报数(1到3),凡是报到3的人推出圈子,问最后留下的是原来第几号的那位?


先说下当时的思路,当时以为留下的人是有规律的,就想用数学归纳法,找到这个规律,但是下面写了结果,发现并没有什么规律。

n=1 , 留下1 ; n=2 , 留下2 ; n=3 , 留下2 ; n=4 , 留下1 ; n=5 , 留下4 ; 

n=6 , 留下1 ; n=7 , 留下4 ; n=8 , 留下7 ; n=9 , 留下1 ; n=10 , 留下4 ; 

数学归纳法的方法不行。那就试试走程序吧。  


经过2个小时的调试,终于出了结果,下面是源码,结合注释看,

#include <stdio.h>void fun(int n){    int a[n];    int i = 0 ;    for(i=0;i<n;i++){        a[i]=i+1;        printf("%d\n",a[i]);    }        int m=0;//用来数123    int k=0;//k记录移动到的a数组下标,k总是有效的数组下标,    int j=0;//记录a数组中大于0的个数,如果只剩一个说明游戏结束。    while(1){                m++;        printf("当前的m=%d   对应的元素  %d\n",m,a[k]);        if(m==3){            a[k]=-1;//把这个元素移除。            m=0;//m归零,重新开始下一轮计数        }              k++;//寻找下一个元素,先向后寻找            for(;k<n;k++){            if(a[k]>0){            printf("向后找到的下一个元素  %d\n",a[k]);            break;            }            }            // 如果向后寻找失败,那就得从头开始继续找        if(k==n){for(i=0;i<n;i++){if(a[i]>0){            k=i;            printf("向前循环找到的下一个元素  %d\n",a[k]);            break;           }            }                    }                        //判断能否结束循环        j=0;        for(i=0;i<n;i++){            if(a[i]>0){                j++;                            }        }        printf("当前数组中有效的元素个数是 %d\n",j);        //j==1,说明可以结束了,k中的就是那个唯一剩下的元素了        if(j==1){            printf("最终留下的数字是 %d\n",a[k]);            break;        }    }            }int main(void) {    fun(10);    return 0;}


下面是log信息: 


12345678910当前的m=1   对应的元素  1向后找到的下一个元素  2当前数组中有效的元素个数是 10当前的m=2   对应的元素  2向后找到的下一个元素  3当前数组中有效的元素个数是 10当前的m=3   对应的元素  3向后找到的下一个元素  4当前数组中有效的元素个数是 9当前的m=1   对应的元素  4向后找到的下一个元素  5当前数组中有效的元素个数是 9当前的m=2   对应的元素  5向后找到的下一个元素  6当前数组中有效的元素个数是 9当前的m=3   对应的元素  6向后找到的下一个元素  7当前数组中有效的元素个数是 8当前的m=1   对应的元素  7向后找到的下一个元素  8当前数组中有效的元素个数是 8当前的m=2   对应的元素  8向后找到的下一个元素  9当前数组中有效的元素个数是 8当前的m=3   对应的元素  9向后找到的下一个元素  10当前数组中有效的元素个数是 7当前的m=1   对应的元素  10向前循环找到的下一个元素  1当前数组中有效的元素个数是 7当前的m=2   对应的元素  1向后找到的下一个元素  2当前数组中有效的元素个数是 7当前的m=3   对应的元素  2向后找到的下一个元素  4当前数组中有效的元素个数是 6当前的m=1   对应的元素  4向后找到的下一个元素  5当前数组中有效的元素个数是 6当前的m=2   对应的元素  5向后找到的下一个元素  7当前数组中有效的元素个数是 6当前的m=3   对应的元素  7向后找到的下一个元素  8当前数组中有效的元素个数是 5当前的m=1   对应的元素  8向后找到的下一个元素  10当前数组中有效的元素个数是 5当前的m=2   对应的元素  10向前循环找到的下一个元素  1当前数组中有效的元素个数是 5当前的m=3   对应的元素  1向后找到的下一个元素  4当前数组中有效的元素个数是 4当前的m=1   对应的元素  4向后找到的下一个元素  5当前数组中有效的元素个数是 4当前的m=2   对应的元素  5向后找到的下一个元素  8当前数组中有效的元素个数是 4当前的m=3   对应的元素  8向后找到的下一个元素  10当前数组中有效的元素个数是 3当前的m=1   对应的元素  10向前循环找到的下一个元素  4当前数组中有效的元素个数是 3当前的m=2   对应的元素  4向后找到的下一个元素  5当前数组中有效的元素个数是 3当前的m=3   对应的元素  5向后找到的下一个元素  10当前数组中有效的元素个数是 2当前的m=1   对应的元素  10向前循环找到的下一个元素  4当前数组中有效的元素个数是 2当前的m=2   对应的元素  4向后找到的下一个元素  10当前数组中有效的元素个数是 2当前的m=3   对应的元素  10向前循环找到的下一个元素  4当前数组中有效的元素个数是 1最终留下的数字是 4


打印杨辉三角

#include <stdio.h>int main(void) {int n=9;int a[n][2*n+1];for(int i=0;i<n;i++){for(int j=0;j<2*n+1;j++){a[i][j]=0;}}//最顶点的1,作为初始值,直接赋值,其余的通过计算获得a[0][n]=1;for(int i=1;i<n;i++){for(int j=0;j<2*n+1;j++){//数组越界了,默认超出的值是0if(j-1<0){a[i][j]=0+a[i-1][j+1];continue;}if(j+1>=2*n+1){a[i][j]=a[i-1][j-1]+0;continue;}//普通的数 = 左上角 + 右上角a[i][j]=a[i-1][j-1]+a[i-1][j+1];}}for(int i=0;i<n;i++){for(int j=0;j<2*n+1;j++){if(a[i][j]==0){// 0 就不打印了,但是占位还是要有的printf("   ") ;continue;}printf(" %d ",a[i][j]) ;}printf("\n");}return 0;}


我设置的参数是9,打印9行,
如果设置10,会有3位数出现,对齐有点问题,需要用\t来对齐,无伤大雅,就不折腾了
结果:
                               1                                                           1     1                                                     1     2     1                                               1     3     3     1                                         1     4     6     4     1                                   1     5     10     10     5     1                             1     6     15     20     15     6     1                       1     7     21     35     35     21     7     1                 1     8     28     56     70     56     28     8     1       



两个乒乓球队进行比赛,各出3人。甲队为A,B,C3人,甲队为x,y,z3人。抽签决定比赛名单。有人向队员打听比赛的名单,A说他不和x比,C说他不和x,z比。编程找出3对赛手的名单。

第一眼以为是好几个结果,但是动手后发现仅有一种符合,这种题其实不适合作为编程题。

#include <stdio.h>int main(void) {// 初看以为会不止一种情况,但是自己动手算出了,发现只有一种情况// a-z  b-x  c-y// i 作为a的对手 j作为b的对手, k作为c的对手,求出ijk即可。char i,j,k;for(i='x';i<='z';i++) {// a的对手不是xif(i=='x') continue;for(j='x';j<='z';j++){if(i==j) continue;for(k='x';k<='z';k++){//c的对手不是x,zif(k=='x'||k=='z') continue;// 对手是抓对厮杀,不能一打Nif(i!=j&&i!=k&&j!=k){printf("a--%c  b--%c  c--%c \n",i,j,k);}}}}return 0;}
 a--z  b--x  c--y




给定一个字符串,同时给定两个字符,求出包含这两个字符的最小子串的长度;
比如输入:addcddabc    ,a  ,c 
那么包含的子串为:addc,   cdda,   abc  —>最小的子串长度为3;
注意, 像是这种有重复的需要处理,比如  addaaaddac,最小字串就是ac,前面重复的a是无效的,只有最后一个有效。
#include <stdio.h>int main(void) {char * str="adadcdcdabc";int start=0;char sChar=' ';for(int i=0;str[i]!='\0';i++){if(str[i]!='a'&&str[i]!='c'){continue;}if(sChar==' '){start=i;sChar=str[i];printf("第一次遇到起始字符 %c  位置为%d \n",sChar,start);continue;} if(str[i]==sChar){start=i;sChar=str[i];printf("遇到重复的起始字符 %c  位置需要更新为%d \n",sChar,start);} else {printf("遇到可以结束的字符 %c  位置为%d 长度为%d \n",str[i],i,i-start+1);sChar=str[i];start=i;printf("新的起始的字符 %c  位置为%d \n",str[i],i,i-start+1);}}return 0;}

第一次遇到起始字符 a  位置为0 遇到重复的起始字符 a  位置需要更新为2 遇到可以结束的字符 c  位置为4 长度为3 新的起始的字符 c  位置为4 遇到重复的起始字符 c  位置需要更新为6 遇到可以结束的字符 a  位置为8 长度为3 新的起始的字符 a  位置为8 遇到可以结束的字符 c  位置为10 长度为3 新的起始的字符 c  位置为10 




这道题初看没什么思路,但是想起了把  这个数分解成两个正整数数乘积的形式 就行,但是对于 2的情况需要特殊考虑,在这个基础上,继续思考,发现不止是2,所有的偶数都是比较特殊的,所以最后就是分为 偶数和奇数个 连续数来分别计算的。
#include <stdio.h>int main(void) {    int n=15;    //从2开始找,3=1+2,最小的符合结果    for(int i = 2; i<=n;i++){        //i一定是偶数,下面还有一个i++,一次循环其实是 i+=2//结果分为偶数个 连续数,和奇数个 连续数// 先处理偶数个的 15=7+8  10=1+2+3+4这种// 15/2==2/2  10/4==4/2  // 也就是 n/偶数 == x.5这种的是可以的        if(n%i==i/2){          //查探一下最小的数是否是一个负数,负数就跳过                //n/i得到平均数-0.5 , i/2得到连续数的一半,在+1就是最小的连续数        //比如 15/2-2/1+1 就是最小的数 7         if(n/i-i/2+1 > 0){        for(int j=0;j<i;j++){         printf(" %d  ",n/i-i/2+1+j);          }        printf("\n");        }            }        // 此时 i 肯定是一个奇数        i++;         // 对奇数取余==0,说明可以整数,商就是连续数的个数        if(n%i==0){                //同样,计算连续数中的最小值,        //n/i是中间值,i/2,i是奇数,得到的值恰好是中位数左边的数量        // 15/5-5/2=1        if(n/i-i/2>0){       for(int j=0;j<i;j++){            printf(" %d  ",n/i-i/2+j);          }        printf("\n");        }        }                    }              return 0;}

结果 

 7   8   4   5   6   1   2   3   4   5  








输出一下内容:



经过观察,# 号 在中间列必然出现,其余的 # 和行数有关, 做一个二维数组来存储,剩下的工作交给调试。


#include <stdio.h>int main(void) {int h = 8 ;int w = 15;char a[h][w];for (int i = 0;i<h;i++){for(int j = 0 ;j<w;j++){a[i][j]=' ';// 最重要的一行,如果   7-i<=j<=7+i,那么符合条件if(h-1+i>=j && h-1-i<=j){a[i][j]='#';}printf("%c",a[i][j]);}printf("\n");}return 0;}


给2个正整数,求2数的最大公约数和最小公倍数:

额~一开始 是懵B的,只是知道能求,只记住了名字叫辗转相除法,至于怎么求,完全不会,还好有百度,

用欧几里德算法(辗转相除法)求两个数的最大公约数的步骤如下:
先用小的一个数除大的一个数,得第一个余数;
再用第一个余数除小的一个数,得第二个余数;
又用第二个余数除第一个余数,得第三个余数;
这样逐次用后一个数去除前一个余数,直到余数是0为止。那么,最后一个除数就是所求的最大公约数(如果最后的除数是1,那么原来的两个数是互质数)。

#include <stdio.h>void func(int n,int m){int a=n;int b=m;// 保证 a是比b大大那个数if(a<b){int temp = a;a = b;b = temp;}int r=0;//r用来保存余数while(b!=0){r=a%b;a=b;b=r;}printf("最大公约数是 %d 最小公倍数是 %d\n",a,n*m/a);}int main(void) {func(3,4);return 0;}




将一个正整数分解质因数,如50分解为2*5*5:

瞬间想到的是递归,但是看别人的都是用的循环,但是还是感觉递归好一点。在i<=n这里小坑了一下,纸上谈兵和真正的调试还是差很多啊。

#include <stdio.h>void func(int n) {if(n==1){return ;}for(int i = 2; i<=n;i++){if(n%i==0){printf(" %d ",i);func(n/i);return;}}}int main(void) {func(98);return 0;}
 2  7  7 





输入一个字符串,输出字符串对应的整数,如“5144”变成int的5144:

细细想来,很多边界条件没有判断啊,负数没判断。如果字符串中像“123aaa213”这样怎么办。

#include <stdio.h>int main(void) {char * a="4722";int sum= a[0]-48 ;for(int i=1;a[i]!='\0';i++){sum = sum *10+a[i]-48;}printf("%d \n",sum);return 0;}


输入一个数字,输出字符串,如5144变成 字符串的 “5144”:

用对10取余获得每一个数字,然后把数字转成对应的char,把char拼起来,但是这样的char是倒序的,那么就倒序打印就好。

#include <stdio.h>int main(void) {int a = 5186214;char str[100];int i=0;while(a>0){ int r = a%10; a=a/10; str[i] = r+48; i++;}for(;i>=0;i--){printf("%c",str[i]);}return 0;}





反转字符串,如“123456”变成“654321”

#include <stdio.h>#include <string.h>int main(void) {char * a= "123456";int len = strlen(a);char b[len+1];char temp;for(int i = len-1,j=0;i>=0;i--,j++ ){b[j]=a[i];}b[len]='\0';printf("%s   %s\n",a,b);return 0;}



查找子串是否存在,如“cd” 在 “abcdef”中是否出现。

记得有一个KMP算法是目前的最优解,算法的时间复杂度最低,效率最高。但是这算法挺难理解的。写个一般的,但是好理解的。

#include <stdio.h>int main(void) {char * a = "aaabcabd";char * b = "abc";for(int i=0,j=0 ;a[i]!='\0';i++){if(a[i]==b[j]){j++;if(b[j]=='\0'){printf("子串存在\n");return 0;}} else{i=i-j;j=0;}}printf("子串不存在\n");return 0;}



一个数组,下表从0-n,元素为从0-n的整数,判断这里面是否有重复元素:

看到题目瞬间就想到了桶排序,申明一个长度为n的数组,遍历原数组,把原数组的元素加入到新数组中,遍历结束后,打印新数组的个数,超过1说明了有重复。

#include <stdio.h>int main(void) {int a[10] = {1,2,2, 4,6,7, 8,1,3, 5};int b[10] = {0,0,0, 0,0,0, 0,0,0, 0};for(int i=0;i<10;i++){b[ a[i] ] ++;}for(int j=0;j<10;j++){printf("%d 出现了 %d 次 \n",j,b[j]);}return 0;}

0 出现了 0 次 1 出现了 2 次 2 出现了 2 次 3 出现了 1 次 4 出现了 1 次 5 出现了 1 次 6 出现了 1 次 7 出现了 1 次 8 出现了 1 次 9 出现了 0 次 




给一个正整数,找出数字1出现的位数,如 421134,1出现在3,4位上:

#include <stdio.h>int main(void) {int a = 4512113;int n=0;while(a>0){int r=a%10;a=a/10;n++;if(r==1){printf("1在%d位上\n",n);}}return 0;}



有一张一元纸币换成1分,2分,5分,每种至少一枚,有多少种换法:

一开始想到了暴力枚举解决,但是这样有点傻,应该有更好的方式:

#include <stdio.h>int main(void) {for(int i=1;i<20;i++){for(int j=1;j<50;j++){for(int k=1;k<94;k++){if(i *5 + j*2 + k == 100){printf("5分 %d 个 , 2分 %d个 , 1分 %d 个\n",i,j,k) ;}}}}return 0;}
5分 1 个 , 2分 1个 , 1分 93 个5分 1 个 , 2分 2个 , 1分 91 个5分 1 个 , 2分 3个 , 1分 89 个5分 1 个 , 2分 4个 , 1分 87 个......5分 19 个 , 2分 2个 , 1分 1 个


超经典的,记得当初学c语言就有这个算法,判断一个数是不是质数:

按照定义求就行了,从2开始遍历,如果这个数对i取余为0,说明是合数,遍历到i *i 还没有,那就说明是质数。

为什么是i*i <n 就能判断了, 因为 n=根号n * 根号n, 如果n是合数,那么这2个乘数肯定一个比根号n大,一个比根号n小,现在遍历到了根号n,这个数还没有找到,那么就能说明,比根号n大的另一个乘数是不存在的,n为质数。

#include <stdio.h>int func(int n){// 其实1 既不是质数,也不是合数。没写那么细if(n<=2){return 1;}for(int i =2 ;i*i<=n;i++){if(n%i==0){printf("是合数");return 0;}}printf("是质数");return 1;}int main(void) {func(18);return 0;}