1. 分类和扩展有什么区别?可以分别用来做什么?分类有哪些局限性?分类的结构体里面有哪些成员?
2.讲一下atomic的实现机制;为什么不能保证绝对的线程安全(最好可以结合场景来说)?
3. 被weak修饰的对象在被释放的时候会发生什么?是如何实现的?知道sideTable么?里面的结构可以画出来么?
4. 关联对象有什么应用,系统如何管理关联对象?其被释放的时候需要手动将所有的关联对象的指针置空么?
5. KVO的底层实现?如何取消系统默认的KVO并手动触发(给KVO的触发设定条件:改变的值符合某个条件时再触发KVO)?
6. Autoreleasepool所使用的数据结构是什么?AutoreleasePoolPage结构体了解么?
7. 讲一下对象,类对象,元类,跟元类结构体的组成以及他们是如何相关联的?为什么对象方法没有保存的对象结构体里,而是保存在类对象的结构体里?
8. class_ro_t和class_rw_t的区别?
9. iOS中内省的几个方法?class方法和objc_getClass方法有什么区别?
10. 在运行时创建类的方法objc_allocateClassPair的方法名尾部为什么是pair(成对的意思)?
11. 一个int变量被__block修饰与否的区别?
12. 为什么在block外部使用__weak修饰的同时需要在内部使用__strong修饰?
13. RunLoop的作用是什么?它的内部工作机制了解么?(最好结合线程和内存管理来说)
14. 哪些场景可以触发离屏渲染?(知道多少说多少)
15. AppDelegate如何瘦身?
16. 反射是什么?可以举出几个应用场景么?(知道多少说多少)
17. 有哪些场景是NSOperation比GCD更容易实现的?(或是NSOperation优于GCD的几点,知道多少说多少)
18. App 启动优化策略?最好结合启动流程来说(main()函数的执行前后都分别说一下,知道多少说多少)
19. App 无痕埋点的思路了解么?你认为理想的无痕埋点系统应该具备哪些特点?(知道多少说多少)
20. 你知道有哪些情况会导致app崩溃,分别可以用什么方法拦截并化解?(知道多少说多少)
21. 你知道有哪些情况会导致app卡顿,分别可以用什么方法来避免?(知道多少说多少)
22. App 网络层有哪些优化策略?
23. TCP为什么要三次握手,四次挥手?
24. 对称加密和非对称加密的区别?分别有哪些算法的实现?
25. HTTPS的握手流程?为什么密钥的传递需要使用非对称加密?双向认证了解么?
26. HTTPS是如何实现验证身份和验证完整性的?
27. 如何用Charles抓HTTPS的包?其中原理和流程是什么?
28. 什么是中间人攻击?如何避免?
29. 了解编译的过程么?分为哪几个步骤?
30. 静态链接了解么?静态库和动态库的区别?
31. 内存的几大区域,各自的职能分别是什么?
32. static和const有什么区别?
33. 了解内联函数么?
34. 什么时候会出现死锁?如何避免?
35. 说一说你对线程安全的理解?
36. 列举你知道的线程同步策略?
37. 有哪几种锁?各自的原理?它们之间的区别是什么?最好可以结合使用场景来说
38. 链表和数组的区别是什么?插入和查询的时间复杂度分别是多少?
39. 哈希表是如何实现的?如何解决地址冲突?
40. 排序题:冒泡排序,选择排序,插入排序,快速排序(二路,三路)能写出那些?
41. 链表题:如何检测链表中是否有环?如何删除链表中等于某个值的所有节点?
42. 数组题:如何在有序数组中找出和等于给定值的两个元素?如何合并两个有序的数组之后保持有序?
43. 二叉树题:如何反转二叉树?如何验证两个二叉树是完全相等的?
分类和扩展的区别:
分类的局限性:
分类的结构体里有哪些成员
struct category_t {
const char *name; //名字
classref_t cls; //类的引用
struct method_list_t *instanceMethods;//实例方法列表
struct method_list_t *classMethods;//类方法列表
struct protocol_list_t *protocols;//协议列表
struct property_list_t *instanceProperties;//实例属性列表
// 此属性不一定真正的存在
struct property_list_t *_classProperties;//类属性列表
};
复制代码
@property(atomic)int age;
,此时编译器会自动生成getter/setter方法,最终会调用objc_getProperty
和objc_setProperty
方法来进行存取属性。若此时属性用atomic
修饰的话,在这两个方法内部使用os_unfair_lock
来进行加锁,来保证读写的原子性。锁都在PropertyLocks中保存着(在iOS平台会初始化8个,mac平台64个),在用之前,会把锁都初始化好,在需要用到时,用对象的地址加上成员变量的偏移量为key,去PropertyLocks中去取。因此存取时用的是同一个锁,所以atomic能保证属性的存取时是线程安全的。注:由于锁是有限的,不用对象,不同属性的读取用的也可能是同一个锁@property(atomic)NSMutableArray *array;
可变的容器时,无法保证对容器的修改是线程安全的objc_getProperty
和objc_setProperty
方法存取属性,在此方法内部保证了读写时的线程安全的,当我们重写getter/setter方法时,就只能依靠自己在getter/setter中保证线程安全被weak修饰的对象在被释放的时候会发生什么?
被weak修饰的对象在被释放的时候,会把weak指针自动置位nil
weak是如何实现的?
runTime会把对weak修饰的对象放到一个全局的哈希表中,用weak修饰的对象的内存地址为key,weak指针为值,在对象进行销毁时,用通过自身地址去哈希表中查找到所有指向此对象的weak指针,并把所有的weak指针置位nil
sideTable的结构
struct SideTable {
spinlock_t slock;//操作SideTable时用到的锁
RefcountMap refcnts;//引用计数器的值
weak_table_t weak_table;//存放weak指针的哈希表
};
复制代码
AssociationsManager
,里面有个AssociationsHashMap
哈希表,哈希表中的key是对象的内存地址,value是ObjectAssociationMap
,也是一个哈希表,其中key是我们设置关联对象所设置的key,value是ObjcAssociation
,里面存放着关联对象设置的值和内存管理的策略。 已void objc_setAssociatedObject(id object, const void * key,id value, objc_AssociationPolicy policy)
为例,首先会通过AssociationsManager
获取AssociationsHashMap
,然后以object
的内存地址为key,从AssociationsHashMap
中取出ObjectAssociationMap
,若没有,则新创建一个ObjectAssociationMap
,然后通过key获取旧值,以及通过key和policy生成新值ObjcAssociation(policy, new_value)
,把新值存放到ObjectAssociationMap
中,若新值不为nil,并且内存管理策略为retain
,则会对新值进行一次retain
,若新值为nil,则会删除旧值,若旧值不为空并且内存管理的策略是retain
,则对旧值进行一次release
_object_remove_assocations
方法来移除所有的关联对象,并根据内存策略,来判断是否需要对关联对象的值进行releaseKVO的底层实现?
当某个类的属性被观察时,系统会在运行时动态的创建一个该类的子类。并且把改对象的isa指向这个子类
假设被观察的属性名是name
,若父类里有setName:
或这_setName:
,那么在子类里重写这2个方法,若2个方法同时存在,则只会重写setName:
一个(这里和KVCset时的搜索顺序是一样的)
若被观察的类型是NSString,那么重写的方法的实现会指向_NSSetObjectValueAndNotify
这个函数,若是Bool类型,那么重写的方法的实现会指向_NSSetBoolValueAndNotify
这个函数,这个函数里会调用willChangeValueForKey:
和didChangevlueForKey:
,并且会在这2个方法调用之间,调用父类set方法的实现
系统会在willChangeValueForKey:
对observe里的change[old]赋值,取值是用valueForKey:
取值的,didChangevlueForKey:
对observe里的change[new]赋值,然后调用observe的这个方法- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context;
当使用KVC赋值的时候,在NSObject里的setValue:forKey:
方法里,若父类不存在setName:
或这_setName:
这些方法,会调用_NSSetValueAndNotifyForKeyInIvar
这个函数,这个函数里同样也会调用willChangeValueForKey:
和didChangevlueForKey:
,若存在则调用
如何取消系统默认的KVO并手动触发(给KVO的触发设定条件:改变的值符合某个条件时再触发KVO)?
举例:取消Person类age属性的默认KVO,设置age大于18时,手动触发KVO
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
if ([key isEqualToString:@"age"]) {
return NO;
}
return [super automaticallyNotifiesObserversForKey:key];
}
- (void)setAge:(NSInteger)age {
if (age > 18 ) {
[self willChangeValueForKey:@"age"];
_age = age;
[self didChangeValueForKey:@"age"];
}else {
_age = age;
}
}
复制代码
Autoreleasepool是由多个AutoreleasePoolPage以双向链表的形式连接起来的, Autoreleasepool的基本原理:在每个自动释放池创建的时候,会在当前的AutoreleasePoolPage中设置一个标记位,在此期间,当有对象调用autorelsease时,会把对象添加到AutoreleasePoolPage中,若当前页添加满了,会初始化一个新页,然后用双向量表链接起来,并把新初始化的这一页设置为hotPage,当自动释放池pop时,从最下面依次往上pop,调用每个对象的release方法,直到遇到标志位。 AutoreleasePoolPage结构如下
class AutoreleasePoolPage {
magic_t const magic;
id *next;//下一个存放autorelease对象的地址
pthread_t const thread; //AutoreleasePoolPage 所在的线程
AutoreleasePoolPage * const parent;//父节点
AutoreleasePoolPage *child;//子节点
uint32_t const depth;//深度,也可以理解为当前page在链表中的位置
uint32_t hiwat;
}
复制代码
讲一下对象,类对象,元类,跟元类结构体的组成以及他们是如何相关联的?
<figcaption></figcaption>
为什么对象方法没有保存的对象结构体里,而是保存在类对象的结构体里?
方法是每个对象互相可以共用的,如果每个对象都存储一份方法列表太浪费内存,由于对象的isa是指向类对象的,当调用的时候,直接去类对象中查找就行了。可以节约很多内存空间的
class_rw_t提供了运行时对类拓展的能力,而class_ro_t存储的大多是类在编译时就已经确定的信息。二者都存有类的方法、属性(成员变量)、协议等信息,不过存储它们的列表实现方式不同。简单的说class_rw_t存储列表使用的二维数组,class_ro_t使用的一维数组。 class_ro_t存储于class_rw_t结构体中,是不可改变的。保存着类的在编译时就已经确定的信息。而运行时修改类的方法,属性,协议等都存储于class_rw_t中
在计算机科学中,内省是指计算机程序在运行时(Run time)检查对象(Object)类型的一种能力,通常也可以称作运行时类型检查。 不应该将内省和反射混淆。相对于内省,反射更进一步,是指计算机程序在运行时(Run time)可以访问、检测和修改它本身状态或行为的一种能力。
iOS中内省的几个方法?
class方法和object_getClass方法有什么区别?
实例class方法就直接返回object_getClass(self),类class方法直接返回self,而object_getClass(类对象),则返回的是元类
因为此方法会创建一个类对象以及元类,正好组成一队
Class objc_allocateClassPair(Class superclass, const char *name,
size_t extraBytes){
...省略了部分代码
//生成一个类对象
cls = alloc_class_for_subclass(superclass, extraBytes);
//生成一个类对象元类对象
meta = alloc_class_for_subclass(superclass, extraBytes);
objc_initializeClassPair_internal(superclass, name, cls, meta);
return cls;
}
复制代码
int变量被__block修饰之后会被包装成一个对象,如__block int age
会被包装成下面这样
struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding; //指向自己
int __flags;
int __size;
int age;//包装的具体的值
};
// age = 20;会被编译成下面这样
(age.__forwarding->age) = 20;
复制代码
用__weak修饰之后block不会对该对象进行retain,只是持有了weak指针,在block执行之前或执行的过程时,随时都有可能被释放,将weak指针置位nil,产生一些未知的错误。在内部用__strong修饰,会在block执行时,对该对象进行一次retain,保证在执行时若该指针不指向nil,则在执行过程中不会指向nil。但有可能在执行执行之前已经为nil了
<figcaption></figcaption>
protocol Command {
func execute()
}
struct InitializeThirdPartiesCommand: Command {
func execute() {
// 初始化第三方库
}
}
struct InitialViewControllerCommand: Command {
let keyWindow: UIWindow
func execute() {
// 设置根控制器
keyWindow.rootViewController = UIViewController()
}
}
struct RegisterToRemoteNotificationsCommand: Command {
func execute() {
// 注册远程推送
}
}
复制代码
然后我们定义StartupCommandsBuilder
来封装如何创建命令的详细信息。APPdelegate调用这个builder去初始化命令并执行这些命令
// MARK: - Builder
final class StartupCommandsBuilder {
private var window: UIWindow!
func setKeyWindow(_ window: UIWindow) -> StartupCommandsBuilder {
self.window = window
return self
}
func build() -> [Command] {
return [
InitializeThirdPartiesCommand(),
InitialViewControllerCommand(keyWindow: window),
RegisterToRemoteNotificationsCommand()
]
}
}
// MARK: - App Delegate
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
StartupCommandsBuilder()
.setKeyWindow(window!)
.build()
.forEach { $0.execute() }
return true
}
}
复制代码
如果APPdelegate需要添加新的职责,则可以创建新的命令,然后把命令添加到builder里去而无需去改变APPdelegate。而且使用命令模式有以下好处
反射是指计算机程序在运行时(runtime)可以访问、检测和修改它本身状态或行为的一种能力。用比喻来说,反射就是程序在运行的时候能够“观察”并且修改自己的行为。
在OC中,反射是指程序在运行时,获取和修改类的信息。 2. 应用场景
App无痕埋点的思路是利用AOP来拦截用户的操作并进行标记记录然后进行上传
我认为理想的无痕埋点系统应该具备以下特点
[NSDictionary initWithObjects:forKeys:]
使用此方法初始化字典时,objects和keys的数量不一致时setObject:forKey:
或者removeObjectForKey:
时,key为nilsetValue:forUndefinedKey:
,使用KVC对对象进行存取值时传入错误的key或者对不可变字典进行赋值1-9都可以利用Runtime进行拦截,然后进行一些逻辑处理,防止crash
三次握手:
四次挥手:
为什么需要三次握手: 为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误,假设这是一个早已失效的报文段。但server收到此失效的连接请求报文段后,就误认为是client再次发出的一个新的连接请求。于是就向client发出确认报文段,同意建立连接。假设不采用“三次握手”,那么只要server发出确认,新的连接就建立了。由于现在client并没有发出建立连接的请求,因此不会理睬server的确认,也不会向server发送数据。但server却以为新的运输连接已经建立,并一直等待client发来数据。这样,server的很多资源就白白浪费掉了。
为什么需要四次挥手: 因为TCP是全双工通信的,在接收到客户端的关闭请求时,还可能在向客户端发送着数据,因此不能再回应关闭链接的请求时,同时发送关闭链接的请求
对称加密,加密的加密和解密使用同一密钥。
非对称加密,使用一对密钥用于加密和解密,分别为公开密钥和私有密钥。公开密钥所有人都可以获得,通信发送方获得接收方的公开密钥之后,就可以使用公开密钥进行加密,接收方收到通信内容后使用私有密钥解密。
对称加密常用的算法实现有AES,ChaCha20,DES,不过DES被认为是不安全的;非对称加密用的算法实现有RSA,ECC
HTTPS的握手流程,如下图,摘自图解HTTP
[图片上传失败...(image-782287-1671505185177)]
<figcaption></figcaption>
答:使用非对称加密是为了后面客户端生成的Pre-master secret密钥的安全,通过上面的步骤能得知,服务器向客户端发送公钥证书这一步是有可能被别人拦截的,如果使用对称加密的话,在客户端向服务端发送Pre-master secret密钥的时候,被黑客拦截的话,就能够使用公钥进行解码,就无法保证Pre-master secret密钥的安全了
答:上面的HTTPS的通信流程只验证了服务端的身份,而服务端没有验证客户端的身份,双向认证是服务端也要确保客户端的身份,大概流程是客户端在校验完服务器的证书之后,会向服务器发送自己的公钥,然后服务端用公钥加密产生一个新的密钥,传给客户端,客户端再用私钥解密,以后就用此密钥进行对称加密的通信
使用数字证书和CA来验证身份,首先服务端先向CA机构去申请证书,CA审核之后会给一个数字证书,里面包裹公钥、签名、有效期,用户信息等各种信息,在客户端发送请求时,服务端会把数字证书发给客户端,然后客户端会通过信任链来验证数字证书是否是有效的,来验证服务端的身份。
使用摘要算法来验证完整性,也就是说在发送消息时,会对消息的内容通过摘要算法生成一段摘要,在收到收到消息时也使用同样的算法生成摘要,来判断摘要是否一致。
流程:
原理:
Charles作为中间人,对客户端伪装成服务端,对服务端伪装成客户端。简单来说:
具体流程如下图,图片来自扯一扯HTTPS单向认证、双向认证、抓包原理、反抓包策略
<figcaption></figcaption>
中间人攻击就是截获到客户端的请求以及服务器的响应,比如Charles抓取HTTPS的包就属于中间人攻击。
避免的方式:客户端可以预埋证书在本地,然后进行证书的比较是否是匹配的
静态链接是指将多个目标文件合并为一个可执行文件,直观感觉就是将所有目标文件的段合并。需要注意的是可执行文件与目标文件的结构基本一致,不同的是是否“可执行”。 静态库:链接时完整地拷贝至可执行文件中,被多次使用就有多份冗余拷贝。 动态库:链接时不复制,程序运行时由系统动态加载到内存,供程序调用,系统只加载一次,多个程序共用,节省内存。
const是指声明一个常量 static修饰全局变量时,表示此全局变量只在当前文件可见 static修饰局部变量时,表示每次调用的初始值为上一次调用的值,调用结束后存储空间不释放
内联函数是为了减少函数调用的开销,编译器在编译阶段把函数体内的代码复制到函数调用处
死锁是指两个或两个以上的线程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。 发生死锁的四个必要条件:
只要上面四个条件有一个条件不被满足就能避免死锁
在并发执行的环境中,对于共享数据通过同步机制保证各个线程都可以正确的执行,不会出现数据污染的情况,或者对于某个资源,在被多个线程访问时,不管运行时执行这些线程有什么样的顺序或者交错,不会出现错误的行为,就认为这个资源是线程安全的,一般来说,对于某个资源如果只有读操作,则这个资源无需同步就是线程安全的,若有多个线程进行读写操作,则需要线程同步来保证线程安全。
链表和数组都是一个有序的集合,数组需要连续的内存空间,而链表不需要,链表的插入删除的时间复杂度是O(1),数组是O(n),根据下标查询的时间复杂度数组是O(1),链表是O(n),根据值查询的时间复杂度,链表和数组都是O(n)
哈希表是也是通过数组来实现的,首先对key值进行哈希化得到一个整数,然后对整数进行计算,得到一个数组中的下标,然后进行存取,解决地址冲突常用方法有开放定址法和链表法。runtime源码的存放weak指针哈希表使用的就是开放定址法,Java里的HashMap使用的是链表法。
这里简单的说下几种快速排序的不同之处,随机快排,是为了解决在近似有序的情况下,时间复杂度会退化为O(n2),双路快排是为了解决快速排序在大量数据重复的情况下,时间复杂度会退化为O(n2),三路快排是在大量数据重复的情况下,对双路快排的一种优化。
extension Array where Element : Comparable{
public mutating func bubbleSort() {
let count = self.count
for i in 0..<count {
for j in 0..<(count - 1 - i) {
if self[j] > self[j + 1] {
(self[j], self[j + 1]) = (self[j + 1], self[j])
}
}
}
}
}
复制代码
extension Array where Element : Comparable{
public mutating func selectionSort() {
let count = self.count
for i in 0..<count {
var minIndex = I
for j in (i+1)..<count {
if self[j] < self[minIndex] {
minIndex = j
}
}
(self[i], self[minIndex]) = (self[minIndex], self[I])
}
}
}
复制代码
extension Array where Element : Comparable{
public mutating func insertionSort() {
let count = self.count
guard count > 1 else { return }
for i in 1..<count {
var preIndex = i - 1
let currentValue = self[I]
while preIndex >= 0 && currentValue < self[preIndex] {
self[preIndex + 1] = self[preIndex]
preIndex -= 1
}
self[preIndex + 1] = currentValue
}
}
}
复制代码
extension Array where Element : Comparable{
public mutating func quickSort() {
func quickSort(left:Int, right:Int) {
guard left < right else { return }
var i = left + 1,j = left
let key = self[left]
while i <= right {
if self[i] < key {
j += 1
(self[i], self[j]) = (self[j], self[I])
}
i += 1
}
(self[left], self[j]) = (self[j], self[left])
quickSort(left: j + 1, right: right)
quickSort(left: left, right: j - 1)
}
quickSort(left: 0, right: self.count - 1)
}
}
复制代码
extension Array where Element : Comparable{
public mutating func quickSort1() {
func quickSort(left:Int, right:Int) {
guard left < right else { return }
let randomIndex = Int.random(in: left...right)
(self[left], self[randomIndex]) = (self[randomIndex], self[left])
var i = left + 1,j = left
let key = self[left]
while i <= right {
if self[i] < key {
j += 1
(self[i], self[j]) = (self[j], self[I])
}
i += 1
}
(self[left], self[j]) = (self[j], self[left])
quickSort(left: j + 1, right: right)
quickSort(left: left, right: j - 1)
}
quickSort(left: 0, right: self.count - 1)
}
}
复制代码
extension Array where Element : Comparable{
public mutating func quickSort2() {
func quickSort(left:Int, right:Int) {
guard left < right else { return }
let randomIndex = Int.random(in: left...right)
(self[left], self[randomIndex]) = (self[randomIndex], self[left])
var l = left + 1, r = right
let key = self[left]
while true {
while l <= r && self[l] < key {
l += 1
}
while l < r && key < self[r]{
r -= 1
}
if l > r { break }
(self[l], self[r]) = (self[r], self[l])
l += 1
r -= 1
}
(self[r], self[left]) = (self[left], self[r])
quickSort(left: r + 1, right: right)
quickSort(left: left, right: r - 1)
}
quickSort(left: 0, right: self.count - 1)
}
}
复制代码
// 三路快排
extension Array where Element : Comparable{
public mutating func quickSort3() {
func quickSort(left:Int, right:Int) {
guard left < right else { return }
let randomIndex = Int.random(in: left...right)
(self[left], self[randomIndex]) = (self[randomIndex], self[left])
var lt = left, gt = right
var i = left + 1
let key = self[left]
while i <= gt {
if self[i] == key {
i += 1
}else if self[i] < key{
(self[i], self[lt + 1]) = (self[lt + 1], self[I])
lt += 1
i += 1
}else {
(self[i], self[gt]) = (self[gt], self[I])
gt -= 1
}
}
(self[left], self[lt]) = (self[lt], self[left])
quickSort(left: gt + 1, right: right)
quickSort(left: left, right: lt - 1)
}
quickSort(left: 0, right: self.count - 1)
}
}
复制代码
public class ListNode {
public var val: Int
public var next: ListNode?
public init(_ val: Int) {
self.val = val
self.next = nil
}
}
extension ListNode {
var hasCycle: Bool {
var slow:ListNode? = self
var fast = self.next
while fast != nil {
if slow! === fast! {
return true
}
slow = slow?.next
fast = fast?.next?.next
}
return false
}
}
复制代码
func remove(with value:Int, from listNode:ListNode?) -> ListNode? {
let tmpNode = ListNode(0)
tmpNode.next = listNode
var currentNode = tmpNode.next
var persiousNode:ListNode? = tmpNode
while currentNode != nil {
if let nodeValue = currentNode?.val, nodeValue == value {
persiousNode?.next = currentNode?.next
}else {
persiousNode = currentNode
}
currentNode = currentNode?.next
}
return tmpNode.next
}
复制代码
func twoSum(_ numbers: [Int], _ target: Int) -> [Int] {
var i = 0, j = numbers.count - 1
while i < j {
let sum = numbers[i] + numbers[j]
if sum == target {
return [i + 1, j + 1]
}else if sum > target {
j -= 1
}else {
i += 1
}
}
return []
}
复制代码
func merge(_ nums1: inout [Int], _ m: Int, _ nums2: [Int], _ n: Int) {
for i in stride(from: n + m - 1, to: n - 1, by: -1) {
nums1[i] = nums1[i - n]
}
var i = 0, j = 0
while i < m && j < n {
if nums1[n + i] > nums2[j] {
nums1[i + j] = nums2[j]
j += 1
}else {
nums1[i + j] = nums1[n + I]
i += 1
}
}
while i < m {
nums1[i + j] = nums1[n + I]
i += 1
}
while j < n {
nums1[i + j] = nums2[j]
j += 1
}
}
复制代码
func invertTree(_ root: TreeNode?) -> TreeNode? {
guard let root = root else { return nil }
(root.left, root.right) = (root.right, root.left)
invertTree(root.left)
invertTree(root.right)
return root
}
复制代码
func isSameTree(_ p: TreeNode?, _ q: TreeNode?) -> Bool {
guard let pNode = p ,let qNode = q else { return q == nil && p == nil }
return pNode.val == qNode.val && isSameTree(pNode.left, qNode.left) && isSameTree(pNode.right, qNode.right)
}
复制代码