Android中高级进阶开发面试题冲刺合集(五)

1,315 阅读25分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第1天,点击查看活动详情

以下主要针对往期收录的面试题进行一个分类归纳整理,方便大家统一回顾和参考。本篇是第五集~

强调一下:因篇幅问题:文中只放部分内容,全部面试开发文档需要的可在公众号<Android苦做舟>获取。

第一篇面试题在这: Android中高级进阶开发面试题冲刺合集(一)

第二篇面试题在这: Android中高级进阶开发面试题冲刺合集(二)

第三篇面试题在这: Android中高级进阶开发面试题冲刺合集(三)

第四篇面试题在这: Android中高级进阶开发面试题冲刺合集(四)

综合技术

1.请谈谈你对 MVC 和 MVP 的理解?

参考答案:

  • MVC 是常用的软件开发架构,但是android中对activity的定位不明确,mvc写法常常会将业务逻辑和视图逻辑都混合在一个activity类中,造成代码逻辑混乱,随着项目增大,维护成本会几何倍增加。
  • MVP 也不是新东西了,主要思想就是将业务逻辑从activity中割裂出来,保证职责的明确,依赖关系可以简单的这样表示 M==>P<===>V ,M只负责获取数据,p只从m拿数据而不关心数据获取的流程,p 会有v的引用,从而发送消息给v ,v需要数据时只需要调用p而不需要知道p对数据的操作。
  • mvp的之间的依赖不是一定的,每个人对一个事物都会有自己的见解,但是万变不离‘职责明确’这四个字
  • mvc使用: 项目小 开发进度需求快,直接莽,用mvc没关系,技术是为了完成业务的 。
  • mvp使用:中大型项目,最好配合模块化,将粒度分的更细更清晰,写起来需要多人配合,不然会稍慢

2.分别介绍下你所知道的 Android 中几种存储方式?

参考答案:

网络存储 : 一般就是http get或http post 从服务器获取数据,业务数据获取的常用办法。 sqllite: 将数据缓存到本地数据库,可用于存储大量不经常改变的数据,可配合contentProvider使用。 文件存储: 将一些不太敏感的数据保存到本地, SharePreference: 用XML格式文件存储数据,在data/data/<pa'ka'geName>/shared_prefs下,不支持数据频繁读写,频繁读写会造成数据错乱。 ContentProvider: 四大组件之一,一般配合sqlite、SharePreference、文件存储使用,支持数据的并发读取。

3.简述下热修复的原理?

参考答案: 热修复分为三个部分,分别是Java代码部分热修复,Native代码部分热修复,还有资源热修复。

资源部分热更新直接反射更改所有保存的AssetManager和Resources对象就行(可能需要重启应用)

Native代码部分也很简单,系统找到一个so文件的路径是根据ClassLoader找的,修改ClassLoader里保存的路径就行(可能需要重启应用)

Java部分的话目前主流有两种方式,一种是Java派,一种是Native派。

  • java派:通过修改ClassLoader来让系统优先加载补丁包里的类 代表作有腾讯的tinker,谷歌官方的Instant Run,包括multidex也是采用的这种方案 优点是稳定性较好,缺点是可能需要重启应用
  • native派:通过内存操作实现,比如方法替换等 代表作是阿里的SopHix,如果算上hook框架的话,还有dexposed,epic等等 优点是即时生效无需重启,缺点是稳定性不好: 如果采用方法替换方式实现,假如这个方法被内联/Sharpening优化了,那么就失效了;inline hook则无法修改超短方法。 热修复后使用反射调用对应方法时可能发生IllegalArgumentException。

4.谈谈你是如何适配更多机型的?

参考答案:

  1. dp原生方案
  2. dimen基于px和dp的适配(宽高限定符和smallestWidth适配)
  3. 头条屏幕适配方案
  4. 头条适配方案改进版本

Android 最全面的屏幕适配方案

5.请谈谈你是如何进行多渠道打包的?

参考答案:

  1. productFlavor
  2. 如果不涉及apk类和资源改动,仅仅是某些配置信息,用walle更快
  3. 第三方的类似腾讯

6.MVP 中你是如何处理 Presenter 层以防止内存泄漏的?

参考答案:

首先 MVP 会出现内存泄漏是因为 Presenter 层持有 View 对象,一般我们会把 Activity 做为 View 传递到 Presenter,Presenter 持有 View对象,Activity 退出了但是没有回收出现内存泄漏。

解决办法: 1.Activity onDestroy() 方法中调用 Presenter 中的方法,把 View 置为 null 2.使用 Lifecycle 3.使用 MVVM

7.如何计算一张图片所占的内存空间大小?

参考答案:

图片占用内存大小=图片宽度图片高度图片位深 图片位深: ARGB_8888 32位、ARGB_4444 16位、ARGB_565 16位 例如: 10801920 ,位深为ARGB_8888的图片的内存占用=1080192032位/8=10801920*4字节= 7.9M

8.有没有遇到 64k 问题,应该如何解决?

参考答案:

1、增加multidel依赖 2、gradle 增加multiDexEnabled true的配置 3、application继承multidexapplication 4、去掉无用的依赖

9.如何优化 Gradle 的构建速度?

参考答案:

1:物理设备,16g+内存,硬盘ssd,高配u。最好是超频u。5.0g那种 2:配置.使用高版本as android.injected.testOnly=false android.buildCacheDir=buildCacheDir org.gradle.caching=true android.enableBuildCache=true android.enableAapt2=true org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 org.gradle.parallel=true org.gradle.configureondemand=true org.gradle.daemon=true android.enableSeparateAnnotationProcessing = true

buildTypes{ debug{

crunchPngs false aaptOptions.cruncherEnabled = false } } dexOptions { preDexLibraries true }

如果使用了multiDexEnabled ,一定要依赖最新版本的1.0.3版本 依赖的库不带+号,每次都会自动检查最新版 3:as设置,如果已下载好依赖,可以设置离线模式

以上配置可以加快构建和编译速度。 环境Mac os 14.as 3.2.0

10.如何获取 Android 设备唯一 ID?

参考答案:

常用方法: IMEI 权限(读手机权限) 可以手动更改(模拟器上) /关闭权限报错

Mac地址 访问WIFI权限 有些设备没有wifi(没有上网功能)/6.0以上版本返回的都是固定的02:00:00 之类地址

ANDROID_ID 设备首次运行,系统会随机生成一个64位的数字,转换成16进制保存 手机恢复出厂设置后值有变化/ 有些国产设备不会返回值

Serial Number (设备序列号) 有些国产手机(红米)会出现垃圾数据

实现思路: 1.获取设备的IMEI -->设备的Mac地址-->随机生成的UUID, 2.拼接在一起,然后用MD5加密 3.存储到本地的文件中,隐藏. 并且存储在App中的sp中 4.使用的时候先从sp中读取, 没有的再生成,然后把生成的唯一id保存到sp中

11.谈一谈 Android P 禁用 HTTP 协议对我们开发有什么影响?

参考答案:

导致Http的请求无效了,http的图片展示不出来了。

最直接的解决方案: 1,让后台换链接,也应该要换 2,target 设置在27及以下。

12.什么是 AOP?在 Android 中它有哪些应用场景?

参考答案:

AOP,面向切面编程,Android里面大多是使用动态代理技术来实现 在日志统计,登录信息check,网络是否连接等场景有应用,主要是用来避免每次做check时都写重复的逻辑代码。

13.什么是 MVVM?你是如何将其应用于具体项目中的?

参考答案:

首先MVVM是一种架构思想。他的主要思想是视图层和model层更加解耦。双向绑定机制使得MVVM更受欢迎,model层改变直接回体现到View层,View层的变动也会体现到Model层。Android目前的双向绑定是由databinding实现的

14.请谈谈你会如何实现数据埋点?

参考答案:

用AspectJ 将埋点代码插入需要的地方,然后每次存入数据库,定期用workmanager 上传,(或每次应用退出后post给workmanager一个上传work

15.假如让你实现断点上传功能,你认为应该怎样去做?

参考答案:

分2种情况: 分块上传:多线程读取本地文件指定区域流上传,header带有上传位置标记,服务器接收到多个上传请求会生成多个上传临时文件接收,最后合并成完整文件。(续传如下) 正常续传:本地请求上传,服务器响应是否未完成的临时文件和文件byte,本地收到就接着指定位置读流上传。

其中会涉及块的数据校验等

16.webp 和 svg 格式的图片各自有什么特点?应该如何在 Android 中使用?

参考答案:

一、svg格式 svg是矢量图,这意味着svg图片由直线和曲线以及绘制它们的方法组成。当你放大一个svg图片的时候,你看到的还是线和曲线,而不会出现像素点。svg图片在放大时,不会失真,所以它非常适合用来绘制企业Logo、Icon SVG格式特点: 1、SVG 指可伸缩矢量图形 (Scalable Vector Graphics) 2、SVG 用来定义用于网络的基于矢量的图形 3、SVG 使用 XML 格式定义图形 4、SVG 图像在放大或改变尺寸的情况下其图形质量不会有所损失 5、SVG 是万维网联盟的标准 6、SVG 与诸如 DOM和 XSL 之类的W3C标准是一个整体 二、webp格式 webp是谷歌开发的一种新图片格式,是同时支持有损和无损压缩的、使用直接色的、点阵图。

17.说说你是如何进行单元测试的?以及如何应用在 MVP 和 MVVM 中?

参考答案:

单元测试库 junit mockito Rebolectric 说下mvp工程中的测试方法 测试主要有 三大部分 1.普通工具类 使用junit 直接测试 2.mvp的p 使用 @mock标注view的接口, 初始化真正的p, 直接调用p的方法 看看 verify view的某些方法是否按照预期被调用 3.mvp的v 用rebolectric 去setup 一个Activity, 然后 用这个库找到 界面上的按钮,或者触发生命周期(onstart),判断一下当前界面的某些view是否被显示 或者 textview的值或者 dialog 是否显示 toast是否弹出错误 4.还有网络部分的测试,可以直接使用junit进行测试 判断下返回值是否符合预期

18.如何绕过 Android 9.0 针对反射的限制?

参考答案:

双重反射,即利用反射调用反射API,这个时候系统进行栈回溯,发现直接调用者是反射API,反射API也是系统API,就直接通过了

19.对于 GIF 格式的图片加载有什么思路和建议?

参考答案:

1、使用GIFLIB+双缓冲的实现,只会创建两个Bitmap,并且内存消耗非常之稳定

2、相比Glide的原生加载,当加载过大的GIF图时,超过了BitmapPool的可用大小,还是会直接创建Bitmap的.

3、使用GIFLIB是直接在native层对GIF数据进行解码的,这一点对Glide来说,效率和内存消耗情况都比较占优.

4、Glide构建当前帧数据和下一帧数据是串行的,而FrameSequenceDrawable则是利用了双缓冲以及解码子线程来实现近似同步的完成上一帧和下一帧数据的无缝衔接的.

20.为什么要将项目迁移到 AndroidX?如何进行迁移?

参考答案:

现在Android官方支持的最低系统版本已经是4.0.1,对应的API版本号是15。support-v4、appcompat-v7库也不再支持那么久远的系统了,但是它们的名字却一直保留了下来,虽然它们现在的实际作用已经对不上当初命名的原因了。 那么很明显,Android团队也意识到这种命名已经非常不合适了,于是对这些API的架构进行了一次重新的划分,推出了AndroidX。因此,AndroidX本质上其实就是对Android Support Library进行的一次升级,升级内容主要在于以下两个方面。 第一,包名。之前Android Support Library中的API,它们的包名都是在android.support.下面的,而AndroidX库中所有API的包名都变成了在androidx. 下面。这是一个很大的变化,意味着以后凡是android.包下面的API都是随着Android操作系统发布的,而androidx. 包下面的API都是随着扩展库发布的,这些API基本不会依赖于操作系统的具体版本。 第二,命名规则。吸取了之前命名规则的弊端,AndroidX所有库的命名规则里都不会再包含具体操作系统API的版本号了。比如,像appcompat-v7库,在AndroidX中就变成了appcompat库。 一个AndroidX完整的依赖库格式如下所示: implementation 'androidx.appcompat:appcompat:1.0.2' 了解了AndroidX是什么之后,现在你应该放轻松了吧?它其实并不是什么全新的东西,而是对Android Support Library的一次升级。因此,AndroidX上手起来也没有任何困难的地方,比如之前你经常使用的RecyclerView、ViewPager等等库,在AndroidX中都会有一个对应的版本,只要改一下包名就可以完全无缝使用,用法方面基本上都没有任何的变化。 但是有一点需要注意,AndroidX和Android Support Library中的库是非常不建议混合在一起使用的,因为它们可能会产生很多不兼容的问题。最好的做法是,要么全部使用AndroidX中的库,要么全部使用Android Support Library中的库。 而现在Android团队官方的态度也很明确,未来都会为AndroidX为主,Android Support Library已经不再建议使用,并会慢慢停止维护。另外,从Android Studio 3.4.2开始,我发现新建的项目已经强制勾选使用AndroidX架构了。

21.你了解过哪些Android屏幕适配方面的技巧?

参考答案:

只保留xxhdpi的资源 使用字节跳动的适配方案,即篡改设备的像素密度参数 density, 可以使UI中写死dp的部分在不同的设备上随着屏幕宽度缩放,致使页面最终效果和UI设计图上和屏幕的比例一致

可以使用android新出的 约束布局 减少页面中需要用线性布局和相对布局嵌套实现的效果,但是线性布局和相对布局中直接能实现的,不需要用约束布局 因为约束布局更占用内存

另外可以根据横屏和竖屏的区别, 在屏幕上添加不同的fragment实现不同的布局

可以使用kotlin的 扩展功能 去扩展Float, 这样可以直接优雅的使用 xxx.dp() 实现px和dp的转换

数据结构方面

1.什么是冒泡排序?如何去优化?

参考答案:

冒泡排序算法原理:(从小到大排序) 1.比较相邻的元素。如果第一个比第二个大,就交换他们两个 2.对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,交换一趟后,最后的元素会是最大的数 3.针对所有的元素重复以上的步骤,除了最后一个 4.持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较

优化方案1(定义一个变量l来保存一趟交换中两两交换的次数,如果l==0,则说明排序已经完成,退出for循环) 优化方案2(假如有一个长度为50的数组,在一趟交换后,最后发生交换的位置是10,那么这个位置之后的40个数必定已经有序了,记录下这位置,下一趟交换只要从数组头部到这个位置就可以了) 定义一个变量n来保存一趟交换中最后一次发生交换的位置,并把它传递给下一趟交换

2.请用 Java 实现一个简单的单链表?

参考答案:

太长了给个链接自己去看: github.com/whatshappen…

3.如何反转一个单链表?

参考答案:

思路:定义3个节点分别是preNode, curNode, nextNode. 先将curNode的next赋值给nextNode, 再curNodel的next指向preNode.至此curNode的指针调整完毕。 然后往后移动curNode, 将curNode赋值给preNode,将nextNode赋值给curNode,如此循环到curNode==null为止

代码: public static Node reverseListNode(Node head){ //单链表为空或只有一个节点,直接返回原单链表 if (head == null || head.getNext() == null){ return head; } //前一个节点指针 Node preNode = null; //当前节点指针 Node curNode = head; //下一个节点指针 Node nextNode = null;

    while (curNode != null){
        nextNode = curNode.getNext();//nextNode 指向下一个节点
        curNode.setNext(preNode);//将当前节点next域指向前一个节点
        preNode = curNode;//preNode 指针向后移动
        curNode = nextNode;//curNode指针向后移动
    }
​
    return preNode;
}

4.谈谈你对时间复杂度和空间复杂度的理解?

参考答案:

在进行算法分析时,语句总的执行次数 T(n) 是关于问题规模 n 的函数,进而分析 T(n) 随 n 变化情况并确定 T(n) 的数量级。算法的时间复杂度,也就是算法的时间度量,记作:T(n) = O(f(n))。它表示随问题规模 n 的增大,算法执行时间的增长率和 f(n) 的增长率相同,称作算法的渐进时间复杂度,简称为时间复杂度。其中 f(n) 是问题规模 n 的某个函数。 这样用大写 O()来体现算法时间复杂度的记法,我们称之为大O记法。随着 n 的增大,T(n) 增长最慢的算法为最优算法

算法的空间复杂度是通过计算算法所需的存储空间实现,记作:S(n) = O(f(n)),其中,n 为问题的规模,f(n) 为语句关于 n 所占存储空间的函数。 若算法执行时间所需要的辅助空间相对于输入数据量而言是个常数,则称此算法为原地工作,空间复杂度为 O(1)。

5.谈一谈如何判断一个链表有环?

参考答案:

一个指针A每次走一步,一个指针B每次走二步,假如有环,那么指针B一直比指针A快,一定会有套圈的一刻存在,即指针A和指针B重叠。没有环的话,就是直到指针B指向null,两个指针还没遇上。

6.手写二叉树结构?

参考答案:

最好包括:

获取某个节点的高度 获取大小 遍历(前序,后续,中序,任选一个吧。)

7.什么是红黑树?为什么要用红黑树?

参考答案:

总结一下:

java 的 HashMap, TreeMap, TreeSet, Linux 的虚拟内存管理; 了解红黑树之前, 先了解二叉搜索树: 1.. 左子树上所有结点的值均小于或等于它的根结点的值; 2.. 右子树上所有结点的值均大于或等于它的根结点的值; 但是这样的二叉树, 很有可能变成线性结构, 为了避免出现这样的情况, 产生了红黑树;

每个节点, 都包含5个属性:color, key, left, right 和 parent; 如果一个节点没有子节点, 或者没有父节点, 则其对应属性值为 NULL; 我们可以把这些 NULL 视为指向二叉搜索树的叶子节点指针, 外部指针; 把带关键字的节点视为树的内部节点;

一般红黑树必须是满足以下属性的二叉搜索树: 1.. 每个节点是红色, 或者是黑色; 2.. 根节点是黑色; 3.. NULL 节点是黑色的; 4.. 如果一个节点是红色的, 那么他的两个子节点都是黑色的; 5.. 每个节点到所有叶子节点, 所经过的黑色节点数量相同; 6.. 最长路径不会超过最短路径的 2 倍;

从某个节点 N 出发, 达到一个叶子节点, 经过任意一个简单路径, 其黑色节点的个数称之为黑高, 记为 bh; 红黑树必须满足, 任意一个节点, 到其任意一个叶子节点, 经过任意的简单路径, 其黑高相等;

左旋传会带上左父亲, 右旋转会带上右父亲;

插入操作

基本描述 要将红黑树当作一棵二叉查找树, 找到插入的位置; 将节点着色为红色(默认就是红色); 通过旋转和重新着色, 对其修正, 使之成为一棵新的红黑树; 因为新插入的节点, 肯定是叶子节点咯, 所以要满足[每个节点到所有叶子节点, 所经过的黑色节点数量相同]; 接下来, 还要依次满足剩余所有的基本属性;

什么情况下需要旋转, 什么情况下不需要旋转? 如果新插入的当前节点的父节点, 是黑色的, 就不需要做任何操作; 如果新插入的当前节点的父节点, 是红色的, 就需要重新着色, 旋转; 红黑树调整的整体思想是, 将红色的节点移到根节点, 然后, 将根节点设为黑色;

调整红黑树 (1)当前节点是根节点, 直接设为黑色的; (2)当前节点的父节点是黑色的, 无序任何操作; (3)当前节点的父节点是红色的, 叔叔节点是红色的; 将父亲节点, 设为黑色; 将叔叔节点, 设为黑色; 将祖父节点, 设为红色; 将祖父节点, 设为新的当前节点, 继续判断;

(4)当前节点的父节点, 是红色的, 叔叔节点是 T.nil 节点或者是黑色的, 当前节点是父节点的右孩子; 将祖父节点设为红色; 将父节点设为黑色; 当前节点的父节点, 设为新的当前节点; 以当前节点, 为支点进行左旋, 继续判断;

(5)当前节点的父节点, 是红色的, 叔叔节点是 T.nil 节点或者是黑色的, 当前节点是父节点的左孩子; 将祖父节点设为红色; 将父节点设为黑色; 当前节点的父节点, 设为新的当前节点; 以当前节点, 为支点进行右旋, 继续判断;

删除操作

待删除的节点情况可以分为 3 种: 1.. 是叶子节点; 2.. 只有左子树或只有右子树; 3.. 有左子树和右子树;

调整红黑树 后继节点, 就是右子树上最小的节点; 前驱节点, 就是左子树上最大的节点; 先看待删除的节点的颜色, 再看兄弟节点的颜色, 先看远侄子再看近侄子, 最后看父亲节点的颜色;

case.. 待删除的节点, 是红色的, 没有子节点, 直接删除既可;

case.. 待删除的节点, 是红色的, 只有一个子节点, 子节点一定是黑色的, 父节点也一定是黑色的; 直接删除当前节点, 子节点替换当前节点, 设为黑色即可;

case.. 待删除的节点, 是红色的, 它有两个子树, 当然左右子树都是黑色的, 父节点是黑色的; 从待删除的右子树, 找到最小的节点; 待删除节点与右子树最小节点, 数值互换; 右子树最小节点作为新的待删除节点, 继续判断;

case.. 待删除的节点, 是黑色的, 只有一个子节点, 子节点是红色的; 直接删除当前节点, 子节点替换当前节点, 设为黑色即可;

case.. 待删除的节点, 是黑色的, 是父节点的左子树, 并且兄弟节点是红色的; 父节点和兄弟节点颜色互换, 以兄弟节点为支点, 左旋传, 继续判断;

case.. 待删除的节点, 是黑色的, 是父节点的左子树, 它的兄弟节点是黑色的, 远侄子是红色的; 父节点和兄弟节点颜色互换, 远侄子设为黑色, 删除当前节点, 以兄弟节点为支点, 左旋传即可;

case.. 待删除的节点, 是黑色的, 是父节点的左子树, 它的兄弟节点是黑色的, 近侄子是红色的; 兄弟节点和近侄子颜色互换, 以兄弟节点为支点, 右旋传, 继续判断;

case.. 待删除的节点, 是黑色的, 是父节点的左子树, 它的兄弟节点是黑色的, 两个侄子都是黑色的; 从待删除的右子树, 找到最小的节点; 待删除节点与右子树最小节点, 数值互换; 右子树最小节点作为新的待删除节点, 继续判断;

case.. 待删除的节点, 是黑色的, 是父节点的右子树, 并且兄弟节点是红色的; 父节点和兄弟节点颜色互换, 以兄弟节点为支点, 右旋传, 继续判断;

case.. 待删除的节点, 是黑色的, 是父节点的右子树, 它的兄弟节点是黑色的, 远侄子是红色的; 父节点和兄弟节点颜色互换, 远侄子设为黑色, 删除当前节点, 以兄弟节点为支点, 右旋转即可;

case.. 待删除的节点, 是黑色的, 是父节点的右子树, 它的兄弟节点是黑色的, 近侄子是红色的; 兄弟节点和近侄子颜色互换, 以兄弟节点为支点, 左旋传, 继续判断;

case.. 待删除的节点, 是黑色的, 是父节点的右子树, 它的兄弟节点是黑色的, 两个侄子都是黑色的; 从待删除的右子树, 找到最小的节点; 待删除节点与右子树最小节点, 数值互换; 右子树最小节点作为新的待删除节点, 继续判断;

case.. 待删除的节点, 是黑色的, 它的兄弟节点是黑色的, 它和兄弟节点都没有子节点; 父节点设为黑色, 兄弟节点设为红色, 删除当前节点即可;

参考

www.cnblogs.com/skywang1234… blog.chinaunix.net/uid-2654823… blog.csdn.net/u010367506/… gengning938.blog.163.com/blog/static… mp.weixin.qq.com/s/ilND8u_8H…

插入 blog.csdn.net/qq_36610462…

删除 www.cnblogs.com/qingergege/… www.cnblogs.com/gcheeze/p/1… my.oschina.net/u/3272058/b… blog.csdn.net/qq_36610462…

在线演示红黑树 www.cs.usfca.edu/~galles/vis… sandbox.runjs.cn/show/2nngvn… files.cnblogs.com/files/bbvi/…

8.什么是快速排序?如何优化?

参考答案:

快速排序是从冒泡排序演变而来的算法,但是其比冒泡排序要高效,所以叫做快速排序,简单理解如下。

我举个简单例子来理解吧:

比如我们即将排序的数组如下:

1  8  9  5  6  3  0

我们一般将首位 1 或者最后一个数字 0 认为是基准元素,然后左右对比,大致规律如下:

第一次 : 将 1 移出,从最右边 数字0 开始,如果 <= 基准数1,则将其移到左边第一个位置,此时 最右边的数字相当于被挖空。

如下,其中 — 代表被挖空的数字

0  8 9 5 6 3 —

接下来从左边开始,如果大于等于基准数1,则将移到右边刚才挖空的位置上,如下:

2 — 9 5 6 3 8

接下来继续从右边开始,刚才右边我们进行到3了,继续左移,如果遇到 <= 基准数 0,那么将其移到刚才 挖的 坑上,如果没有遇到,并且左右操作的数相同时,此时 将 基准数 移动到这个空着的坑位。

如下:

0 1 9 5 6 3 8

我们可以发现,基准数1左边的都小于其,右边的都大于其,所以两边各自继续按照刚才上面的逻辑继续递归。(虽然这里最左边只是0,可以忽略)

接下来的过程如下:

0 15 6 3 8   (基准数9)
​
0 1 8 5 6 3 —
​
0 1 8 5 6 3 9  
0 15 6 3 9 (基准数8)
​
0 1 3 5 6 9 _
​
0 1 3 5 6 _ 9
0 1 3 5 6  8 9  (基准数6

优化:

  • 快速排序在序列中元素很少时,效率将比较低,不如插入排序,按需使用。

  • 基准数采用随机。

  • 尾递归优化。

    快速排序和分治排序算法一样,都有两次递归调用,而且快排的递归在尾部,所以我们可以对快排代码实施尾递归优化。减少堆栈深度。

  • 将每次分割结束后,将于本次基数相等的元素聚集在一起,再次分割时,避免对聚集过的元素进行分割。

  • 多线程优化,基于分治法的思想,将一个规模为 n 的问题分解为 k个规模较小的问题。这些子问题互相独立且与原问题相同。求解这些子问题,然后将子问题的解合并,从而得到原问题的解。

9.如何判断单链表交叉?

参考答案:

如果两个链表相交的话,则它们的尾结点一定是相同的,呈横向的“Y”字型,那么最简单的方式就是分别遍历两个链表到结尾,对比结尾的值是否相等就可以了