题目:找出两个 UIView 的最近的公共 View,如果不存在,则输出 nil 。

分析:这其实是数据结构里面的找最近公共祖先的问题。

一个 UIViewController 中的所有 view 之间的关系其实可以看成一颗树,UIViewController 的 view 变量是这颗树的根节点,其它的 view 都是根节点的直接或间接子节点。

所以我们可以通过 view 的 superview 属性,一直找到根节点。需要注意的是,在代码中,我们还需要考虑各种非法输入,如果输入了 nil,则也需要处理,避免异常。以下是找到指定 view 到根 view 的路径代码:

+ (NSArray *)superViews:(UIView *)view {
if (view == nil) {
return @[];
}
NSMutableArray *result = [NSMutableArray array];
while (view != nil) {
[result addObject:view];
view = view.superview;
}
return [result copy];
}

然后对于两个 view A 和 view B,我们可以得到两个路径,而本题中我们要找的是这里面最近的一个公共节点。

一个简单直接的办法:拿第一个路径中的所有节点,去第二个节点中查找。假设路径的平均长度是 N,因为每个节点都要找 N 次,一共有 N 个节点,所以这个办法的时间复杂度是 O(N^2)。

+ (UIView *)commonView_1:(UIView *)viewA andView:(UIView *)viewB {
NSArray *arr1 = [self superViews:viewA];
NSArray *arr2 = [self superViews:viewB];
for (NSUInteger i = ; i < arr1.count; ++i) {
UIView *targetView = arr1[i];
for (NSUInteger j = ; j < arr2.count; ++j) {
if (targetView == arr2[j]) {
return targetView;
}
}
}
return nil;
}

一个改进的办法:我们将一个路径中的所有点先放进 NSSet 中。因为 NSSet 的内部实现是一个 hash 表,所以查找元素的时间复杂度变成了 O(1),我们一共有 N 个节点,所以总时间复杂度优化到了 O(N)。

+ (UIView *)commonView_2:(UIView *)viewA andView:(UIView *)viewB {
NSArray *arr1 = [self superViews:viewA];
NSArray *arr2 = [self superViews:viewB];
NSSet *set = [NSSet setWithArray:arr2];
for (NSUInteger i = ; i < arr1.count; ++i) {
UIView *targetView = arr1[i];
if ([set containsObject:targetView]) {
return targetView;
}
}
return nil;
}

除了使用 NSSet 外,我们还可以使用类似归并排序的思想,用两个「指针」,分别指向两个路径的根节点,然后从根节点开始,找第一个不同的节点,第一个不同节点的上一个公共节点,就是我们的答案。代码如下:

/* O(N) Solution */
+ (UIView *)commonView_3:(UIView *)viewA andView:(UIView *)viewB {
NSArray *arr1 = [self superViews:viewA];
NSArray *arr2 = [self superViews:viewB];
NSInteger p1 = arr1.count - ;
NSInteger p2 = arr2.count - ;
UIView *answer = nil;
while (p1 >= && p2 >= ) {
if (arr1[p1] == arr2[p2]) {
answer = arr1[p1];
}
p1--;
p2--;
}
return answer;
}

我们还可以使用 UIView 的 isDescendant 方法来简化我们的代码,不过这样写的话,时间复杂度应该也是 O(N^2) 的。lexrus 提供了如下的 Swift 版本的代码:

/// without flatMap
extension UIView {
func commonSuperview(of view: UIView) -> UIView? {
if let s = superview {
if view.isDescendant(of: s) {
return s
} else {
return s.commonSuperview(of: view)
}
}
return nil
}
}

特别地,如果我们利用 Optinal 的 flatMap 方法,可以将上面的代码简化得更短,基本上算是一行代码搞定。怎么样,你学会了吗?

extension UIView {
func commonSuperview(of view: UIView) -> UIView? {
return superview.flatMap {
view.isDescendant(of: $) ?
$ : $.commonSuperview(of: view)
}
}
}

iOS 面试题(一):寻找最近公共 View --转自唐巧的更多相关文章

  1. iOS开发寻找最近公共view

    新技能 #pragma mark --寻找最近公共view + (NSArray *)superViews:(UIView *)view{ if (view==nil) { return @[]; } ...

  2. iOS之面试题:腾讯三次面试以及参考思路

    使用了第三方库, 有看他们是怎么实现的吗? 例:SD.YY.AFN.MJ等! <1>.SD为例: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 ...

  3. 大厂常问iOS面试题--性能优化篇

    1.造成tableView卡顿的原因有哪些? 1.最常用的就是cell的重用, 注册重用标识符 如果不重用cell时,每当一个cell显示到屏幕上时,就会重新创建一个新的cell 如果有很多数据的时候 ...

  4. 最全的iOS面试题及答案-转载

    1. Object-c的类可以多重继承么?可以实现多个接口么?Category是什么?重写一个类的方式用继承好还是分类好?为什么? 答: Object-c的类不可以多重继承:可以实现多个接口,通过实现 ...

  5. iOS面试题及答案2015.6.7

    iOS面试题及答案     1. Object-c的类可以多重继承么?可以实现多个接口么?Category是什么?重写一个类的方式用继承好还是分类好?为什么? 答: Object-c的类不可以多重继承 ...

  6. [转载]iOS面试题总

    转载自:http://blog.sina.com.cn/s/blog_67eb608b0101r6xb.html (2014-06-13 20:23:33) 转载▼ 标签: 转载   crash 原文 ...

  7. 试答卓同学的 iOS 面试题

    卓同学昨天写了一篇文章<4道过滤菜鸟的iOS面试题>.我手痒决定默写一个参考答案.后来发现不认真回答被大家喷成狗,所以决定积极改造,重新做人.下面就是修编之后的答案. 1. struct和 ...

  8. iOS面试题大全-点亮你iOS技能树

    所有的内容大部分来自于网络的搜集,所以我不是一个创造者,而是一个搬运工.我尽量把题目,尤其是参考答案的出处列明.若有任何疑问,建议,意见,请联系我. 第一部分面试题来源于iOS-Developer-I ...

  9. 原 iOS面试题收集

    原 iOS面试题收集 发表于2年前(2013-07-22 13:47)   阅读(369) | 评论(0) 4人收藏此文章, 我要收藏 赞0 听云性能监测产品App.Server.CDN免费试用,绑定 ...

随机推荐

  1. Knockout.js随手记(5)

    以列表方式呈现数据  处理以数组形式储存的多条数据,要先认识foreach.在ViewModel定义一个JavaScript Array或是ko.observableArray() (observab ...

  2. MVC Controller Dependency Injection for Beginners【翻译】

    在codeproject看到一篇文章,群里的一个朋友要帮忙我翻译一下顺便贴出来,这篇文章适合新手,也算是对MEF的一个简单用法的介绍. Introduction In a simple stateme ...

  3. Html 移动web开发细节处理

    1.-webkit-tap-highlight-color:rgba(255,255,255,0)可以同时屏蔽ios和android下点击元素时出现的阴影.备注:transparent的属性值在and ...

  4. mysql数据库链接与创建

    有童鞋问到说,环境搭建好了,mysql也安装了,但是就是进不去数据库,也启动不了,一直报错,那么下面这边就说下如何用Navicat链接上创建的数据库 首先 1)在xshell里进入mysql,命令是: ...

  5. python安装库

    首先确保安装了pip,并且pip也加入了系统path路径: pip下载:https://pypi.python.org/pypi/pip#downloads 下载Python对应的包:(http:// ...

  6. lua52 C API测试代码

    //这是一篇lua与C++交互的情景测试 #include <lua.hpp> #include <lauxlib.h> #include <lualib.h> # ...

  7. 分布式缓存技术redis学习系列(三)——redis高级应用(主从、事务与锁、持久化)

    上文<详细讲解redis数据结构(内存模型)以及常用命令>介绍了redis的数据类型以及常用命令,本文我们来学习下redis的一些高级特性. 安全性设置 设置客户端操作秘密 redis安装 ...

  8. XML序列化及反序列化

    //对象序列化xml OutModel outmodel = new OutModel(); XmlSerializer serializer = new XmlSerializer(typeof(O ...

  9. android--handler

    app在启动时会产生一个进程和一个线程,线程是主线程,又叫UI线程,更新UI元素必须要在UI线程中更新,否则会报错. 在UI线程中有消息队列,子线程sendMessage到MQ中,looper类取出队 ...

  10. hibernate 的SessionFactory的getCurrentSession 与 openSession() 的区别

    1 getCurrentSession创建的session会和绑定到当前线程,而openSession不会. 2 getCurrentSession创建的线程会在事务回滚或事物提交后自动关闭,而ope ...