五十一道19年精选面试题+十一道常问算法=提高面试成功率
如有需要请认真看完 ~ 结尾有惊喜彩蛋哦
-
KVO实现原理
当观察某对象时,runtime机制会动态为该对象添加一个子类,并为子类重写被观察属性的setter方法,由setter方法通知被观察属性的改变状态。触发KVO的主要是调用willChangeValueForKey和didChangeValueForKey。 -
类和结构体的区别
类是引用类型,结构体是值类型。结构体变量分配在栈,OC对象分配在堆。结构体只能封装属性,类不仅可以封装属性也可以封装方法。 -
堆和栈的区别
栈是由编译器自动分配释放,存储局部和临时变量,栈的存储空间是连续的;堆是由程序员自己分配释放的,堆的存储空间是不连续的;栈对象分配内存速度快,在栈中不会发生内存泄漏,堆对象可以自己掌控生命周期,但是容易造成内存泄漏。 -
开发中遇到的内存泄漏
使用AFN时候需要用到SessionManager,如果多次创建SessionManager就会造成内存泄漏,此时需要我们创建SessionManager单例进行全局管理;循环引用导致的内存泄漏,block的循环引用,delegate的循环引用,NSTimer的循环引用。 -
property修饰符
1、原子性 atomic,对属性读写操作时候会加锁会造成性能损耗,但不等于线程安全;nonatomic,对属性读写时候不会加锁,提高读写效率;
2、读写权限 readwrite,可读可写,既有setter方法又有getter方法,readonly,只读,只有getter方法;
3、内存管理 strong,指向并拥有该对象,修饰的对象引用计数会加1;weak,指向但不拥有该对象,修饰的对象引用计数不会加1,代理用weak修饰避免循环引用;copy,与strong类似,不同的是strong的复制是多个指针指向同一个地址,copy的复制是在内存中复制一份对象,指针指向不同地址,一般用在修饰有对应可变类型的不可变对象上,block用copy修饰;assign,直接赋值,用于基本数据类型。 -
weak和assign的区别
weak一般用于修饰对象类型,assign一般用于修饰基本数据类型,assign修饰的对象被释放后,指针的地址依然存在,会造成野指针,而weak修饰的对象被释放后,指针会自动被置为nil。 -
block为什么要用copy修饰
block创建时候默认分配在栈区,作用域仅限创建时候的当前上下文,可能随时被收回,为了能在作用域外调用block,在操作的时候不被收回,需要将block从栈区复制到堆区。 -
block的实现原理
block是C语言的扩充,是一个自动包含局部变量的匿名函数。block其实就是一个OC对象,而这个OC对象又是一个结构体,在结构体中他有6个属性,ISA指针,指向对象的类;flags,按bit位表示block的附加信息;reserved,保留变量;invoke函数指针,指向具体block实现的函数调用地址;descriptor,表示block的附加信息;variables,block捕获到的变量参数,block就是通过这些参数访问外部变量的。 -
什么时候用weak
1、代理用weak修饰,避免循环引用,并且不会造成野指针错误;
2、storyboard或xib拖拽生成的属性用weak修饰,用storyboard或xib创建的控件视图已经对其有着引用关系。 -
weak修饰的对象释放后为什么会被置为nil(weak实现原理)
weak其实是runtime维护的一张哈希表,用于存储指向某个对象的所有weak指针。其中key是所指对象的地址,value是weak指针的地址数组。初始化时候,runtime调用objc_initWeak()函数,初始化一个新的weak指针指向对象的地址,引用时候,objc_initWeak()调用objc_storeWeak()函数,更新指针指向,并创建对应弱引用表,释放时,调用clearDeallocating()函数,根据对象地址获取weak指针的数组,遍历数组将其中的元素置为nil,最后从weak表中删除,清除对象的记录。 -
block和delegate的区别
block的运行成本高,出栈操作需要将使用的数据从栈内存拷贝到堆内存,delegate的运行成本低,只保存一个对象指针直接回调;block可以很方便的修改上下文变量,delegate只能通过参数传递方式修改;block代码集中,可读性高,delegate代码分散,并且需要声明和实现;delegate用weak修饰就能避免循环引用,block稍不注意就会导致循环引用。 -
对runtime的理解
OC是运行时语言,对象类型的确定由编译时推迟到运行时,在runtime中对象用结构体表示,ISA指针,实例对象指向类,类对象指向元类;super_class,指向父类;ivars,存储成员变量;methodLists,存储方法;protocols,存储协议方法;instance_size,确定对象长度;cache,对象缓存。通过这些属性,我们可以获取对象的成员变量列表、属性列表、方法列表、协议列表等;可以交换方法,动态添加方法;可以动态添加类,为类添加实例变量,为分类添加属性。 -
runtime的消息发送机制
当我们向对象发送消息时,编译器在运行时会转化为objc_msgSend()函数,转化时候除了方法本身的参数以外,还隐藏又两个参数,一个是id类型表示对象类型,一个是SEL类型,是函数对应的编号。对象通过ISA找到其对应的类,通过SEL去对应的类的方法列表中找,如果未找到就去父类中找,一直找到NSObject类。如果仍未找到,此时runtime会给我们三次机会保证程序不崩溃,第一次,动态方法解析,通过resolveClassMethod()函数或resolveInstanceMethod()函数提供一次添加方法的机会,如果添加一个函数并返回YES,就会执行这个函数,否则就等待重定向,通过forwardingTargetForSelector函数返回一个实现该方法的对象,如果返回self或NO,就消息转发,通过methodSignatureForSelector()函数,将方法参数返回值打包成一个NSInvocation对象,通过forwardInvacation()函数将这个Invocation对象发送给提供一个方法实现的对象。 -
ISA是什么
ISA是一个Class类型的指针,每个实例对象都有一个ISA指针,指向对象的类,每个Class也有一个ISA指针,指向元类,元类也是有一个ISA指针,最终指向根类,而根类的ISA指针指向自己,最终形成一个封闭的内循环。 -
load和initialize的区别
load:类被应用时执行,每个类的load方法只调用一次,当父类和子类都实现了load方法,父类先调用,子类后调用,当子类未实现oad方法,父类的load方法仍会调用;类中的load方法优先于分类中的load方法,分类的load方法调用顺序和导入工程的顺序一致,即compile source中的顺序。
initialize:类的第一个方法被调用前执行,由系统自动调用。无论子类是否实现initialize方法,都会执行父类的initialize方法,当子类实现initialize方法时,子类执行子类的initialize方法方法;当子类未实现initialize方法时,子类执行父类的initialize方法;分类的initialize方法会覆盖主类的initialize方法。 -
GCD和NSOperation的区别
GCD是基于C语言实现的,能够利用多核CPU的优势处理多线程充分发挥CPU的使用;NSOperation基于cocoa框架实现的,更加面向对象,可以监听线程的执行状态,可以很容易管理线程间的依赖关系,能够设置最大并发数。 -
GCD资源竞争和死锁
导致资源竞争的原因是,两个线程争夺同一资源。因此解决资源竞争的方法就是线程在拥有资源的时候对资源加锁,另一线程只有等待解锁以后才可以拥有该资源。
两个线程相互等待对方释放资源就会导致死锁,最常见的死锁是使用dispatch_sync。解决死锁的办法:异步执行。 -
iOS常见的锁
同步锁;信号量;互斥锁;对象锁;条件锁;自旋锁;读写锁;递归锁 -
对ARC内存管理的理解
OC的内存管理就是对引用计数的管理,ARC就是自动引用计数,在我们编译时候编译器会分析源码中每个对象的生命周期,基于这些周期添加对应的引用计数。 -
自动释放池的实现原理
每个线程都有一个runloop,并且维护着自己的自动释放池,由于子线程的runloop默认是不开启的,也就意味着子线程不会自动创建自动释放池。自动释放池以栈的形式实现,当runloop开始一次循环时候就会创建自动释放池,它将被添加到栈顶,调用了对象的autorelease方法,这个对象就会被存储到栈顶的自动释放池中,当自动释放池满或者一次runloop循环结束时候就会向自动释放池中的对象发release消息,自动释放池被回收。 -
自动释放池在runloop中如何工作的
APP启动,在主线程runloop中注册了两个observe,第一个observe监听即将进入runloop,在其回调内调用objc_autoreleasePoolPush()创建自动释放池,优先级最高,保证创建自动释放池在其他操作之前;第二个observe监听两个事件:即将进入睡眠,调用objc_autoreleasePoolPop()和objc_autoreleasePoolPush()释放旧池并创建新池;即将退出runloop时调用objc_autoreleasePoolPop()释放自动释放池,其observe优先级最低,保证释放池释放在其他所有回调之后。 -
对runloop的理解
runloop是一个对象,这个对象在循环中处理程序运行中出现的各种事件,触摸事件、UI刷新事件、定时器事件、方法事件等,从而保持程序的持续运行,在没有事件处理时候进入睡眠模式,从而节省CPU资源,提高程序性能。
每个线程都有对应的唯一一个runloop,主线程的runloop在程序启动时候就自动创建,其他线程的runloop都需要手动启动。runloop并不是线程安全的,因此要避免在其他线程调用当前线程的runloop。 -
runloop的mode和源
mode:
NSDefaultRunLoopMode,默认
UITrackingRunLoopMode,scrollView滑动时
UIInitializationRunLoopMode,程序启动时
NSEventTrackingRunLoopMode,界面跟踪触摸事件时
NSRunLoopCommonModes,mode集
源:
定时器源和输入源,其中输入源有source0和source1,source0自定义输入源,source1基于端口的输入源。 -
如何创建一个常驻线程
创建一个线程,在线程中开启runloop,添加match_port的输入源,调用run方法启动runloop。 -
tableView如何优化
1、cell复用
2、复杂业务处理放在子线程
3、视图尽量避免使用透明颜色,尽量减少使用CALayer
4、cell高度缓存
5、数据预加载
6、自定义cell,视图使用懒加载,尽量减少addSubView给cell动态添加View
7、耗时的UI操作可以通过CFRunLoopRef对象处理 -
cell复用机制
tableView有一个visibleCells数组保存显示的cell和reusableTableCells字典保存可复用的cells以及对应的id,显示第一屏cell时候,reusableTableCells为空,dequeueReusableCell获取cell时候返回nil,通过初始化方法创建cell,当第一个cell完全移除屏幕时候,这时候需要再显示一个cell,从reusableTableCells中取到为空,再初始化一个添加到visibleCells中,同时第一个cell就从visibleCells中移除,添加到reusableTableCells中,之后再有cell移除屏幕时reusableTableCells就有值,就可以正常复用。
cell复用会调用哪个方法?prepareForReuse -
NSCache和NSDictionary的区别
NSCache是线程安全的,在多线程操作时不需要加锁,NSCache在系统发出低内存通知时能够自动删除缓存,NSCache只是对对象的抢引用,对象不需要实现NSCoding协议。 -
NSString、NSArrary、NSDictionary用什么修饰
NSString用copy修饰,NSArray和 NSDictionary根据实际需求,如果有可变数组或可变字典赋值给NSSArray或NSDictionary用copy修饰,可变对象的copy是深复制,即把整个对象内存都复制到另外一块内存中,这样对原对象做任何操作都不会影响新对象的内容。 -
对HTTP、TCP、UDP的理解
TCP/IP是一个协议组,分为三个层次:应用层、传输层、网络层,在应用层有 HTTP协议,在传输层有TCP、UDP协议,在网络层有IP协议。
HTTP是应用层的超文本传输协议,HTTP协议是建立在请求/响应模型上的,首先由客户端建立一条与服务器的TCP链接,并发送请求,请求中包含请求方法、URI、协议版本以及相关的MIME样式消息,服务端响应状态,返回协议版本、成功失败码以及相关的MIME样式消息。
TCP和UDP都是基于传输层的协议,TCP是基于连接的协议,需要经过三次握手建立连接,因此传输速度慢,而UDP是面向非连接的协议,因此会出现丢包的情况。 -
TCP的三次握手
第一次握手:客户端向服务器发起连接请求时,向服务器发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;
第二次握手:服务器收到syn包,确认客户端的SYN(ack=j+1),同时自己也发一个SYN包(syn=k),即SYN+ACK,服务器进入SYN_RECV状态;
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包(ack=k+1),客户端和服务器TCP连接成功。 -
ISO的七层模型
应用层、表示层、会话层、传输层、网络层、数据链路层、物理层 -
websocket和Socket的区别
websocket是建立在TCP之上的应用层协议,和HTTP不同的是它是双向通信协议。Socket其实是为了方便使用TCP或UDP抽象出来的位于传输层和应用层之间的一组接口。 -
APP启动时间优化
首先要知道APP启动的过程,APP启动会加载资源文件,动态库,初始化runtime,执行类的load方法,applicationdidfinish方法加载window和第一个VC,最终显示第一个画面。优化启动时间就尽量减少导入资源文件,删除无用的资源文件和动态库,减少使用storyboard和xib,减少在applicationdidfinish中的耗时操作。 -
MVC的优缺点
优点:代码总量少,简单易懂
缺点:代码过于集中,难以进行测试,难以扩展,网络请求逻辑无处安放 -
Category的实现原理,Category为什么不能添加实例变量
分类的实现原理是将category中的方法,属性,协议数据放在category_t结构体中,然后将结构体内的方法列表拷贝到类对象的方法列表中。
Category可以添加属性,但是并不会自动生成成员变量及set/get方法。因为category_t结构体中并不存在成员变量。对象的成员变量是存放在实例对象中的,并且编译的那一刻就已经决定好了。而分类是在运行时才去加载的。那么我们就无法在程序运行时将分类的成员变量中添加到实例对象的结构体中。因此分类中不可以添加成员变量。 -
Category中有load方法吗?load方法是什么时候调用的?load 方法能继承吗?
Category中有load方法,load方法在程序启动装载类信息的时候就会调用。load方法可以继承。调用子类的load方法之前,会先调用父类的load方
37.事件传递流程
发生一个触摸事件后,系统会把这个事件放在UIApplication管理的事件队列中,UIApplication充事件队列中取到第一个事件,将事件分发给window,window寻找处理事件最合适的View,调用View的touchs方法处理具体事件。
-
应用如何找到最合适的控件来处理事件
1.首先判断主窗口(keyWindow)自己是否能接受触摸事件
2.判断触摸点是否在自己身上
3.子控件数组中从后往前遍历子控件,重复前面的两个步骤(所谓从后往前遍历子控件,就是首先查找子控件数组中最后一个元素,然后执行1、2步骤)
4.view,比如叫做fitView,那么会把这个事件交给这个fitView,再遍历这个fitView的子控件,直至没有更合适的view为止。
5.如果没有符合条件的子控件,那么就认为自己最合适处理这个事件,也就是自己是最合适的view。 -
UIView什么情况下不能接收触摸事件
不允许交互:userInteractionEnabled = NO
隐藏:如果把父控件隐藏,那么子控件也会隐藏,隐藏的控件不能接受事件
透明度:如果设置一个控件的透明度<0.01,会直接影响子控件的透明度。alpha:0.0~0.01为透明。
默认UIImageView不能接受触摸事件,因为不允许交互,即userInteractionEnabled = NO。所以如果希望UIImageView可以交互,需要设置UIImageView的userInteractionEnabled = YES。 -
如何绘制UIView?
绘制一个UIView最灵活的方法就是由它自己完成绘制。实际上你不是绘制一个UIView,而是子类化一个UIView并赋予绘制自己的能力。当一个UIView需要执行绘制操作时,drawRect:方法就会被调用,覆盖此方法让你获得绘图操作的机会。当drawRect:方法被调用,当前图形的上下文也被设置为属于视图的图形上下文,你可以使用Core Graphic或者UIKit提供的方法将图形画在该上下文中。 -
什么是MVVM?主要目的是什么?优点有哪些?
MVVM即 Model-View-ViewModel
1.View主要用于界面呈现,与用户输入设备进行交互、
2.ViewModel是MVVM架构中最重要的部分,ViewModel中包含属性,方法,事件,属性验证等逻辑,负责View与Model之间的通讯
3.Model就是我们常说的数据模型,用于数据的构造,数据的驱动,主要提供基础实体的属性。
MVVM主要目的是分离视图和模型
MVVM优点:低耦合,可重用性,独立开发,可测试 -
get请求与post请求的区别
1.get是向服务器发索取数据的一种请求,而post是向服务器提交数据的一种请求
2.get没有请求体,post有请求体
3.get请求的数据会暴露在地址栏中,而post请求不会,所以post请求的安全性比get请求号
4.get请求对url长度有限制,而post请求对url长度理论上是不会收限制的,但是实际上各个服务器会规定对post提交数据大小进行限制。 -
谈谈你对多线程开发的理解?ios中有几种实现多线程的方法?
好处:
- 使用多线程可以把程序中占据时间长的任务放到后台去处理,如图片,视频的下载;
- 发挥多核处理器的优势,并发执行让系统运行的更快,更流畅,用户体验更好;
缺点: - 大量的线程降低代码的可读性;
- 更多的线程需要更多的内存空间;
- 当多个线程对同一个资源出现争夺的时候要注意线程安全的问题。
ios有3种多线程编程的技术:1.NSThread,2.NSOperationQueue,3.gcd;
- XMPP工作原理;xmpp系统特点
原理:
- 所有从一个client到另一个client的jabber消息和数据都要通过xmpp server
- client链接到server
- server利用本地目录系统的证书对其认证
- server查找,连接并进行相互认证
- client间进行交互
特点: - 客户机/服务器通信模式;
- 分布式网络;
- 简单的客户端;
- XML的数据格式
-
地图的定位是怎么实现的?
1.导入了CoreLocation.framework
2.ios8以后,如果需要使用定位功能,就需要请求用户授权,在首次运行时会弹框提示
3.通过本机自带的gps获取位置信息(即经纬度) -
苹果内购实现流程
程序通过bundle存储的plist文件得到产品标识符的列表。程序向App Store发送请求,得到产品的信息。App Store返回产品信息。程序把返回的产品信息显示给用户(App的store界面)用户选择某个产品程序向App Store发送支付请求App Store处理支付请求并返回交易完成信息App获取信息并提供内容给用户。 -
支付宝,微信等相关类型的sdk的集成
1.在支付宝开发平台创建应用并获取APPID
2.配置密钥
3.集成并配置SDK
4.调用接口(如交易查询接口,交易退款接口) -
gcd产生死锁的原因及解锁的方法
产生死锁的必要条件:1.互斥条件,2.请求与保持条件,3.不剥夺条件,4.循环等待条件。
解决办法:采用异步执行block。 -
生成二维码的步骤
1.使用CIFilter滤镜类生成二维码
2.对生成的二维码进行加工,使其更清晰
3.自定义二维码背景色、填充色
4.自定义定位角标
5.在二维码中心插入小图片 -
在使用XMPP的时候有没有什么困难
发送附件(图片,语音,文档...)时比较麻烦XMPP框架没有提供发送附件的功能,需要自己实现
实现方法,把文件上传到文件服务器,上传成功后获取文件保存路径,再把附件的路径发送给好友 -
是否使用过环信,简单的说下环信的实现原理
环信是一个即时通讯的服务提供商
环信使用的是XMPP协议,它是再XMPP的基础上进行二次开发,对服务器Openfire和客户端进行功能模型的添加和客户端SDK的封装,环信的本质还是使用XMPP,基于Socket的网络通信
环信内部实现了数据缓存,会把聊天记录添加到数据库,把附件(如音频文件,图片文件)下载到本地,使程序员更多时间是花到用户体验体验上。
没看过瘾 搜素QQ群 776598941 加入iOS社区群 收获更多精选面试题或者点击此处直接进群
算法
一、对以下一组数据进行降序排序(冒泡排序)。“24,17,85,13,9,54,76,45,5,63”
int main(int argc, char *argv[]) {
int array[10] = {24, 17, 85, 13, 9, 54, 76, 45, 5, 63};
int num = sizeof(array)/sizeof(int);
for(int i = 0; i < num-1; i++) {
for(int j = 0; j < num - 1 - i; j++) {
if(array[j] < array[j+1]) {
int tmp = array[j];
array[j] = array[j+1];
array[j+1] = tmp;
}
}
}
for(int i = 0; i < num; i++) {
printf("%d", array[i]);
if(i == num-1) {
printf("\n");
}
else {
printf(" ");
}
}
}
二、对以下一组数据进行升序排序(选择排序)。“86, 37, 56, 29, 92, 73, 15, 63, 30, 8”
void sort(int a[],int n)
{
int i, j, index;
for(i = 0; i < n - 1; i++) {
index = i;
for(j = i + 1; j < n; j++) {
if(a[index] > a[j]) {
index = j;
}
}
if(index != i) {
int temp = a[i];
a[i] = a[index];
a[index] = temp;
}
}
}
int main(int argc, const char * argv[]) {
int numArr[10] = {86, 37, 56, 29, 92, 73, 15, 63, 30, 8};
sort(numArr, 10);
for (int i = 0; i < 10; i++) {
printf("%d, ", numArr[i]);
}
printf("\n");
return 0;
}
三、 快速排序算法
void sort(int *a, int left, int right) {
if(left >= right) {
return ;
}
int i = left;
int j = right;
int key = a[left];
while (i < j) {
while (i < j && key >= a[j]) {
j--;
}
a[i] = a[j];
while (i < j && key <= a[i]) {
i++;
}
a[j] = a[i];
}
a[i] = key;
sort(a, left, i-1);
sort(a, i+1, right);
}
四、归并排序
void merge(int sourceArr[], int tempArr[], int startIndex, int midIndex, int endIndex) {
int i = startIndex;
int j = midIndex + 1;
int k = startIndex;
while (i != midIndex + 1 && j != endIndex + 1) {
if (sourceArr[i] >= sourceArr[j]) {
tempArr[k++] = sourceArr[j++];
} else {
tempArr[k++] = sourceArr[i++];
}
}
while (i != midIndex + 1) {
tempArr[k++] = sourceArr[i++];
}
while (j != endIndex + 1) {
tempArr[k++] = sourceArr[j++];
}
for (i = startIndex; i <= endIndex; i++) {
sourceArr[i] = tempArr[i];
}
}
void sort(int souceArr[], int tempArr[], int startIndex, int endIndex) {
int midIndex;
if (startIndex < endIndex) {
midIndex = (startIndex + endIndex) / 2;
sort(souceArr, tempArr, startIndex, midIndex);
sort(souceArr, tempArr, midIndex + 1, endIndex);
merge(souceArr, tempArr, startIndex, midIndex, endIndex);
}
}
int main(int argc, const char * argv[]) {
int numArr[10] = {86, 37, 56, 29, 92, 73, 15, 63, 30, 8};
int tempArr[10];
sort(numArr, tempArr, 0, 9);
for (int i = 0; i < 10; i++) {
printf("%d, ", numArr[i]);
}
printf("\n");
return 0;
}
五、 实现二分查找算法(编程语言不限)
int bsearchWithoutRecursion(int array[],int low,int high,int target) {
while(low <= high) {
int mid = (low + high) / 2;
if(array[mid] > target)
high = mid - 1;
else if(array[mid] < target)
low = mid + 1;
else //findthetarget
return mid;
}
//the array does not contain the target
return -1;
}
递归实现
int binary_search(const int arr[],int low,int high,int key)
{
int mid=low + (high - low) / 2;
if(low > high)
return -1;
else{
if(arr[mid] == key)
return mid;
else if(arr[mid] > key)
return binary_search(arr, low, mid-1, key);
else
return binary_search(arr, mid+1, high, key);
}
}
六、如何实现链表翻转(链表逆序)?
思路:每次把第二个元素提到最前面来。
#include <stdio.h>
#include <stdlib.h>
typedef struct NODE {
struct NODE *next;
int num;
}node;
node *createLinkList(int length) {
if (length <= 0) {
return NULL;
}
node *head,*p,*q;
int number = 1;
head = (node *)malloc(sizeof(node));
head->num = 1;
head->next = head;
p = q = head;
while (++number <= length) {
p = (node *)malloc(sizeof(node));
p->num = number;
p->next = NULL;
q->next = p;
q = p;
}
return head;
}
void printLinkList(node *head) {
if (head == NULL) {
return;
}
node *p = head;
while (p) {
printf("%d ", p->num);
p = p -> next;
}
printf("\n");
}
node *reverseFunc1(node *head) {
if (head == NULL) {
return head;
}
node *p,*q;
p = head;
q = NULL;
while (p) {
node *pNext = p -> next;
p -> next = q;
q = p;
p = pNext;
}
return q;
}
int main(int argc, const char * argv[]) {
node *head = createLinkList(7);
if (head) {
printLinkList(head);
node *reHead = reverseFunc1(head);
printLinkList(reHead);
free(reHead);
}
free(head);
return 0;
}
七、 实现一个字符串“how are you”的逆序输出(编程语言不限)。如给定字符串为“hello world”,输出结果应当为“world hello”。
int spliterFunc(char *p) {
char c[100][100];
int i = 0;
int j = 0;
while (*p != '\0') {
if (*p == ' ') {
i++;
j = 0;
} else {
c[i][j] = *p;
j++;
}
p++;
}
for (int k = i; k >= 0; k--) {
printf("%s", c[k]);
if (k > 0) {
printf(" ");
} else {
printf("\n");
}
}
return 0;
}
八、 给定一个字符串,输出本字符串中只出现一次并且最靠前的那个字符的位置?如“abaccddeeef”,字符是b,输出应该是2。
char *strOutPut(char *);
int compareDifferentChar(char, char *);
int main(int argc, const char * argv[]) {
char *inputStr = "abaccddeeef";
char *outputStr = strOutPut(inputStr);
printf("%c \n", *outputStr);
return 0;
}
char *strOutPut(char *s) {
char str[100];
char *p = s;
int index = 0;
while (*s != '\0') {
if (compareDifferentChar(*s, p) == 1) {
str[index] = *s;
index++;
}
s++;
}
return &str;
}
int compareDifferentChar(char c, char *s) {
int i = 0;
while (*s != '\0' && i<= 1) {
if (*s == c) {
i++;
}
s++;
}
if (i == 1) {
return 1;
} else {
return 0;
}
}
九、二叉树的先序遍历为FBACDEGH,中序遍历为:ABDCEFGH,请写出这个二叉树的后序遍历结果。
ADECBHGF
先序+中序遍历还原二叉树:先序遍历是:ABDEGCFH 中序遍历是:DBGEACHF
首先从先序得到第一个为A,就是二叉树的根,回到中序,可以将其分为三部分:
左子树的中序序列DBGE,根A,右子树的中序序列CHF
接着将左子树的序列回到先序可以得到B为根,这样回到左子树的中序再次将左子树分割为三部分:
左子树的左子树D,左子树的根B,左子树的右子树GE
同样地,可以得到右子树的根为C
类似地将右子树分割为根C,右子树的右子树HF,注意其左子树为空
如果只有一个就是叶子不用再进行了,刚才的GE和HF再次这样运作,就可以将二叉树还原了。
十、 打印2-100之间的素数。
int main(int argc, const char * argv[]) {
for (int i = 2; i < 100; i++) {
int r = isPrime(i);
if (r == 1) {
printf("%ld ", i);
}
}
return 0;
}
int isPrime(int n)
{
int i, s;
for(i = 2; i <= sqrt(n); i++)
if(n % i == 0) return 0;
return 1;
}
十一、 求两个整数的最大公约数。
int gcd(int a, int b) {
int temp = 0;
if (a < b) {
temp = a;
a = b;
b = temp;
}
while (b != 0) {
temp = a % b;
a = b;
b = temp;
}
return a;
}
总结
以上就是为大家整理的在IOS面试中可能会遇到的常见算法问题和答案,希望这篇文章对大家的面试能有一定的帮助,如果有疑问大家可以加群 776598941探讨或者留言交流。
2019阿里巴巴技术面试题集锦!
链接:https://pan.baidu.com/s/1LX_a_xQjuMO10tHpsYrvGg
提取码:xikv