几个面试算法题,附源码

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

 A,B,C,D,E五人在某天夜里合伙去捕鱼, 第二天凌晨时都已疲惫不堪,于是各自找地方睡觉。
 日上三杆
 A第一个醒来,他将鱼分成五份,把多余的一条鱼扔掉,拿走自己的一份;
 B第二个醒来,也将鱼分成五份,把多余的一条鱼扔掉,拿走自己的一份;
 C,D,E依次醒来,也同样这样做 。

问他们合伙捕了多少条鱼?

先假设一个数满足了条件, 这些条件依次带入 ,带入5次后还没有出现小数即为成功 . 打印这个数即可 . see 是每个人看见的总鱼数. 

// 验证这个数字是否满足条件
func tryThisNum(tolte:Int)->Bool {
    
    var see = tolte
    for i in 0..<5 {
        if (see-1)%5==0 {
            see = (see-1)/5*4
        } else {
            return false
        }
    }
    // 循环了5次,还没有返回,说明满足条件了
    return true
    
}


for k in 0 ..< 10000 {
    if tryThisNum(tolte: k) {
        print( String(k) + "是满足要求的鱼数")
    }
}
3121是满足要求的鱼数
6246是满足要求的鱼数
9371是满足要求的鱼数

在 n 个数中找到 前 m 大的数字,除了排序还有什么思路。

第一反应有点懵,后来想想,找出m个大数,那就需要一个数组b来存,然后拿b中最小值依次与n个数进行比较并更新b中的值即可。后来百度了,说有种堆排序的方法,适合大量数据。下面是思路:

维护一个大小为m的小根堆,依次扫描n个无序数,若扫描到的数比堆顶元素要大,用扫描到的数替换堆顶元素,然后调整堆。这个算法的思想源自堆排序。时间复杂度是O(nlogm)。适用于海量数据,即它不需要所有的数据都加载进内存,只需要维护一个大小为m的小顶堆,这是其一个巨大的优势。

#include <stdio.h>

// 纪录最大值和最大值的下标
int minV=0,minX=0;
// 找出b中的最小值
void findMinInB(int *b){
	
	minV = b[0];
	minX = 0 ;
	for (int i=0;b[i];i++){
		if (minV>b[i]){
			minV=b[i];
			minX = i ;
		}
	}
	printf("最小值 %d 最小值下标 %d \n",minV ,minX);
}

void show(int * a){
	for (int i=0;a[i];i++){
		printf("%d \t",a[i]);
	}
	printf("\n");
}

int main()
{
   int a[10]={1,5,12,7,187,
			 6,78,45,11,26};
	int b[5]={0,0,0,0,0};
	// 给b中的值初始值
	for (int i=0;i<5;i++) {
		b[i] = a[i];
	}
	// 找到b中的最小者,纪录下来坐标和值
	findMinInB(b);
	// 用剩下的a中的值依次进行比较
	for (int i=5;a[i];i++){
		// 比b中最小值大的话就替换最小值,然后再次更新b中最小者
		if(a[i]>minV){
			b[minX]=a[i];
			findMinInB(b);
		}
	}
	show(a);
	show(b);
	
   return 0;
}

打印杨辉三角

#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++){
			//数组越界了,默认超出的值是0
			if(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的对手不是x
		if(i=='x') continue;
		
		for(j='x';j<='z';j++){
			if(i==j) continue;
			
			for(k='x';k<='z';k++){
				//c的对手不是x,z
				if(k=='x'||k=='z') continue;
				// 对手是抓对厮杀,不能一打N
				if(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  

输出以下内容:

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

#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,那么原来的两个数是互质数)。
先用小的一个数除大的一个数,得第一个余数;
再用第一个余数除小的一个数,得第二个余数;
又用第二个余数除第一个余数,得第三个余数;
这样逐次用后一个数去除前一个余数,直到余数是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”这样怎么办。如果是是一个空字符串""怎么处理, 

如果要是考察程序的严谨性, 那这个就是0分啊. 

#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;
}

 

©️2020 CSDN 皮肤主题: 精致技术 设计师:CSDN官方博客 返回首页