腾讯常见面试题总结

后台 + 测开 

计算机网络

TCP三次握手和四次挥手的过程?握手为什么不是两次?

第一次握手:客户端发送SYN包到服务器,并进入SYN_SEND状态,等待服务器确认。

第二次握手:服务器收到SYN包,确认客户端的SYN包,同时自己也发送一个SYN包,即SYN+ACK,此时服务器进入SYN_RECV状态。

第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包,此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。

采用三次握手是为了防止失效的连接请求报文段又传送到服务器端产生错误:若客户端第一次发送的连接请求并没丢失,由于网络原因被阻塞到了某个节点,阻塞时间超过定时器时间。客户端则会认为该消息丢失 ,重新发送该消息。当第一次发送的连接请求到达服务器,服务器认为这是一个新的请求连接消息,向客户端发送确认。对客户端而言,由于超时,所以认为第一次连接结束,并不理会这个确认消息。服务器便会一直等待客户端的消息,导致服务器资源浪费。

第一次挥手:A发送一个FIN,用来关闭A到B的数据传输,A进入FIN_WAIT_1状态

第二次挥手:B收到FIN后,发送给ACK给A,B进入CLOSE_WAIT状态

                      A收到来自B的ACK后,进入FIN_WAIT_2状态

第三次挥手:B发送一个FIN,用来关闭B到A的数据传输,B进入LAST_ACK状态

第四次挥手:A收到FIN后,A进入TIME_WAIT状态,发送ACK给B,B进入CLOSED状态,A在2MSI后进入CLOSED状态

四次挥手的TIME_WAIT状态什么时候存在,为什么存在,为什么要等待两个时间周期

在第三次挥手之后,A马上回应最后的ACK,但是不能立即进入CLOSE状态

理由一:A不能保证最后的ACK到达B,如果最后的ACK丢失,B发起了FIN重传,如果 A处于CLOSED状态,就没法给B发送ACK了。所以A应该等待一段时间,即在TIME_WAIT状态。

理由二:让网络中有关此次网络传输的包全部消失

TCP和UDP的区别

  • TCP是面向连接的服务,而UDP是无连接的,即发送数据之前不需要建立连接
  • TCP提供可靠交付服务,无差错,不丢失,不重复,按序到达,而UDP提供尽力而为交付服务,不保证可靠交付
  • TCP面向字节流,而UDP面向报文
  • TCP具有拥塞控制,力求使每一条TCP连接公平的享用网络资源。UDP没有拥塞控制,网络出现拥塞的时候不会使源主机的发送速率降低(对实时应用很有用)
  • TCP连接只能是点到点的,而UDP支持一对一,一对多,多对一和多对多的交互通信
  • TCP首部开销20字节,UDP首部开销8字节
  • TCP的逻辑通信信道是全双工的可靠信道,而UDP是不可靠信道

UDP能否实现可靠连接 

可以,将可靠传输服务转移到应用层中实现。

应用层中增加seq/ack机制:确保数据发送到对端

增加发送缓冲区和接收缓冲区:超时重传

添加超时重传机制

七层网络模型

OSI参考模型

应用层:HTTP, HTTPS, FTP, SMTP, DNS, TELNET, POP3, IMAP, DHCP, RIP

表示层:数据的表示,安全,压缩

会话层:建立,管理,终止会话

传输层:TCP, UDP

网络层:IP, ICMP, IGMP

数据链路层:ARP, PPP

物理层

http的状态码有哪些?204,304?

2xx  成功

  • 200  OK  请求已成功
  • 201   Created  服务器依照客户端请求创建一个新资源
  • 204   Not Content   服务器拒绝对PUT, POST或者HELETE请求返回任何状态信息或表示

3xx  重定向

  • 301   Moved Permanently  该资源被永久迁移
  • 302  Moved Temporarily  暂时被迁移
  • 304   Not Modified  客户端已有该数据,没必要重复发送

4xx   客户端错误

  • 400  Bad Request  服务器不懂什么意思
  • 404  Not Found  被请求的文档不在服务器上

5xx   服务端错误

  • 500  Internal Server Error  执行请求处理代码遇到了异常
  • 505   HTTP Version Not Supported:服务器不支持请求报文使用的HTTP协议版本

GET和POST的区别

  • GET参数通过URL传递,POST参数则被放在请求体中
  • GET请求在URL传递的参数的长度是有限制的,而POST没有
  • GET参数直接暴露在URL中,没有POST安全
  • GET请求只能进行URL编码,而POST支持多种编码方式
  • GET产生一个TCP数据包(header和data一起发出去),POST产生两个TCP数据包(先发header后发data)

http和https的区别

  • https协议需要到CA申请证书,可能需要一定的费用
  • http是超文本传输协议,信息是明文传输。https则是具有安全性的ssl加密传输协议
  • http和https采用不同的连接方式,http用80端口,https用443端口
  • http连接简单,无状态。https使由http+SSL构建的可进行加密传输、身份认证的网络协议

TCP的流量控制与拥塞控制

  • 流量控制(通过滑动窗口来实现):让发送方的发送速率不要太快,让接收方来得及接受。
  • 拥塞控制(慢启动,拥塞避免,快速恢复):防止过多的数据注入到网络中。

如何测试一个登陆网页

有一个登陆页面(有两个textbox,一个提交按钮),设计30个以上的test  case

  • 功能测试

什么都不输入,点击提交按钮,看提示信息

输入正确的用户名或密码,点击提交按钮,验证能否正确登陆

输入错误的用户名或者密码,验证登陆失败,并给出相应的错误信息

登陆成功后能否跳转到正确的页面

用户名和密码,太短或者太长的处理

用户名和密码中有特殊字符的处理

记住用户名,记住密码功能

登陆失败时候,不能记住密码

用户名及密码前后有空格的处理

密码是否加密显示

如果有验证码,文字是否扭曲过度导致辨认难度大,颜色,刷新是否好用

登陆页面中的注册,忘记密码,注销后用另一账户登陆的链接是否正确

输入密码的时候大写键盘开启的提示信息

  • 界面测试

布局是否合理,2个textbox和一个按钮是否对齐

textbox和按钮的长度,高度是否符合要求

界面的设计风格是否和整体风格一致

界面中的文字简洁易懂,没有错别字

  • 性能测试

打开登陆页面需要几秒

输入正确的用户名和密码后,登陆成功跳转到新页面的时间

  • 安全性测试

登陆成功后生成cookie,是否是http only

用户名和密码是否通过加密的方式发送给web服务器

用户名和密码的验证,应该是由服务器端验证,而不是在客户端用javascript验证

用户名和密码的输入框,应屏蔽SQL注入攻击

用户名和密码的输入框,应禁止输入脚本

错误登陆的次数限制(防止暴力破解)

考虑是否支持多用户在同一机器上的登陆

考虑是否支持同一用户在多台机器上登陆

  • 可用性测试

是否可以全用键盘操作,是否有快捷键

输入用户名密码后按回车,是否可以登陆

输入框是否能TAB键切换

  • 兼容性测试

主流的浏览器下是否正常显示且功能正常

不同的平台是否能正常工作

移动设备上是否能正常工作

不同的分辨率之下

  • 本地化测试

不同语言环境下,界面能否正确显示

  • 软件辅助性测试(是否向残疾用户提供足够的辅助功能)

高对比度下能否显示正常

tcp为什么是可靠的,怎么保证

校验和,序列号,定时器,确认应答

快重传

TCP采用累计确认机制,当接收到三个冗余ACK后,重新发送该报文段

操作系统

进程和线程的区别

  • 进程是具有一定独立功能的程序关于某个数据集合上的一次活动,是系统进行资源分配和调度的一个独立单位。
  • 线程是进程的一个实体,是CPU调度和分派的基本单位。属于同一个进程的线程可以共享进程所拥有的全部资源。

进程间的通信方式,哪个快

  • 管道:半双工,具有固定的读端和写端。 只能用于父子进程或兄弟进程之间的通信。存于内存中的一种特殊文件。
  • 命名管道:可在无关的进程之间交换数据。以一种特殊文件的形式存在于文件系统中。
  • 消息队列:存放于内核中,一个消息队列由一个标识符来标识。面向记录,独立于发送与接收进程,可以实现消息的随机查询
  • 信号量:用于实现进程的互斥和同步。
  • 共享内存:速度快,能够控制容量,但是要保持同步。

fork  vfork  clone

三个函数都是用来创建新进程

  • fork()  不需要任何参数,仅仅创建一个子进程并为其创建一个独立于父进程的空间。采用写时复制机制。
  • vfork()   创建一个子进程,共享父进程的空间。创建子进程之后,父进程阻塞,直到子进程执行了exec()或exit()。
  • clone()   创建线程,可以指定新的命名空间,有选择的继承父进程内存等

孤儿进程 僵尸进程 危害

  • 孤儿进程:一个父进程退出,而它的一个或者多个子进程还在运行,那么这些子进程将成为孤儿进程。被init进程所收养,并由init进程对它们完成状态收集工作。
  • 僵尸进程:一个进程使用fork创建子进程,如果子进程退出,而父进程没有调用wait获取子进程的状态信息,那么子进程的进程描述符仍保留在系统中。
  • 僵尸进程的危害:如果父进程不调用wait的话,保留的那段信息就不会释放,其进程号一直被占用。系统所能使用的进程号是有限的,可能会导致没有可用的进程号而不能产生新的进程。
  • 解决僵尸进程的办法:kill掉父进程,由init进程处理

并发和并行的区别?

  • 并发是指一个CPU同时处理多个任务
  • 并行是指在多个CPU或者是多核处理器上同时处理多个不同的任务。

linux解压命令,查看连接状态,查看进程,查看内存,cpu占用命令,端口占用命令

  • 解压命令       unzip 文件名.zip  /  tar -xvf 文件名.tar.gz
  • 查看连接状态  netstat
  • 查看运行中的进程  ps
  • 杀死运行中的进程   kill  pid
  • 查看内存  free
  • 查看CPU占用  top
  • 查看端口  netstat  -anp | grep 端口号

CPU大端和小端存储

  • 大端模式,是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中。地址由小向大增加,而数据从高位往低位放
  • 小端模式,是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中。

C++

什么是结构体?

结构体是一系列数据的集合。这个数据可能描述了一个物体,也可能是对一个问题的抽象。

一般在什么情况下用到结构体?

  • 一般当内置内存无法满足用户需要,没有合适类型对应对象时,需要封装特定的类型。
  • 当函数有多个参数的时候,返回值过多,需要封装特定类型,将参数打包返回。

什么是结构体内存对齐?为什么要对齐?怎样对齐?

  • 元素是按照定义顺序一个一个放到内存中去的,但并不是紧密排列的。从结构体存储的首地址开始,每个元素放置到内存中,它都会认为内存是按照自己的大小来划分的,因此元素放置的位置一定会在自己宽度的整数倍上开始。
  • 从零偏移处开始,按字节大小计算,判断此偏移地址是否是该成员变量和对齐参数两者之间的最小值。
  • 若是,则从此处开始占用内存,大小为该类型所占字节数值,若不是,则内存向后偏移到最小值整数倍处,再开始占用空间。
  • 按前两步骤算出结构体实际所占内存时,为方便后面类型的存储,再向后偏移一位,然后判断该地址是否是默认对齐参数与该结构体中最大类型所占字节数的最小值,若是,则当前偏移地址的字节数便是结构体大小,若不是 ,继续向后偏移,直至为最小整数倍为止。

对齐参数如何设置?可以设置为按照任意字节数对齐吗?

  • 在windows中,默认对齐参数为8
  • 在linux中,默认对齐参数为4
  • 设置对齐参数可在结构体struct之前加上#pragma pack(对齐数),在struct后加上#pragma pack;
  • 对其参数不能任意设置,只能是内置类型已有的字节数。

如何知道结构体某个成员相对于结构体起始位置的偏移量?

使用offsetof来判断结构体中成员的偏移地址。需要用到stddef.h头文件

堆和栈的区别

  • 管理方式:堆中资源由程序员控制(通过malloc/free、new/delete,容易产生碎片问题),栈资源由编译器自动管理。
  • 系统响应:对于堆,系统中有一个记录空闲内存地址的链表,当系统收到程序申请时,遍历该链表,寻找第一个大于所申请空间的空间的堆节点,删除空闲节点链表中的该节点,并将该节点空间分配给程序。对于栈,只要栈的剩余空间大于所申请空间,系统就会为程序分配内存,否则报异常出现栈空间溢出错误。
  • 空间大小:堆是不连续的内存区域,大小受限于计算机系统中有效的虚拟内存,空间灵活,比较大。栈是一块连续的存储区域,大小是操作系统预定好的。
  • 碎片问题:对于堆,频繁的new/delete会造成大量内存碎片。栈不会产生碎片。
  • 生长方向:堆向高地址方向增长,栈向低地址方向增长。
  • 分配方式 :堆是动态分配。栈有静态分配和动态分配,静态分配由编译器完成,动态分配由mallac函数分配,但栈的动态分配资源由编译器自动释放。
  • 分配效率:堆由C/C++函数库提供,效率比栈低很多。栈是机器系统提供的数据结构,计算机在底层对栈提供支持,分配专门的寄存器存放栈地址 ,提供栈操作专门的指令。

vector,deque,map,list,set,hashmap的实现机制

  • vector的数据安排与操作和数组类似。数组是静态空间而vector是动态数组,在堆中分配内存。随着元素的加入,vector会自动扩充空间容纳新的元素,影响vector的效率。对最后的元素操作最快,对任何元素的访问都是O(1)的时间。常用来保存需要经常进行随机访问的内容,并且不需要对中间元素进行添加删除操作。
  • list每次插入或删除一个元素,就配置或者释放一个元素空间,元素也是在堆中。对于任何位置元素的插入和删除 ,是常数时间。list底层是一个环状双向链表,随机存储没有效率。
  • deque是一种双开口的连续线性空间,元素在堆中。允许常数时间内对起头端进行元素的插入和移除操作。
  • map是基于红黑树的实现方式 ,即添加到一个有序列表,在O(log n)的复杂度内通过key找到value,优点是空间要求低,但在实现上不如 hashmap。
  • hashmap是基于hashcode的实现方式,在查找上比mao速度快,添加时也没有任何顺序,但是空间复杂度高。
  • set集合,包含了经过排序了的数据,通过红黑树实现

红黑树有什么特点

  • 红黑树是一种自平衡的二叉搜索树,相对于AVL的完全平衡,红黑树只要求局部平衡,因此在添加和删除节点时,调整比AVL小,性能高于AVL树。
  • 每个节点都是黑色或者红色,根节点是黑色。
  • 每个叶子(为空的叶子节点)结点是黑色。
  • 如果一个节点是红色的,则它的子节点必须是黑色的
  • 从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。

new和malloc的区别

  • new/delete是C++的关键字,需要编译器支持。malloc/free是库函数,需要头文件支持。
  • 使用new操作符申请内存分配时无需指定内存块的大小,编译器自动计算。malloc需要显示指出所需要内存的尺寸。
  • new内存分配成功时,返回的是对象类型的指针,无需进行类型转换,是符合类型安全性的操作符。而malloc内存分配成功则返回void*,需要通过强制类型转换将void*转换为我们需要的类型。
  • new内存分配失败时会抛出异常,而malloc分配内存失败则返回null。
  • new会先申请足够的内存,然后调用类的构造函数,初始化成员变量,最后返回自定义类型的指针。delete先调用析构函数,然后释放内存。malloc/free是库函数 ,只能动态的申请和释放内存 ,无法强制要求做自定义类型对象构造和析构。
  • C++允许重载new/delete操作符。malloc不允许重载。

封装,继承,多态,虚函数

  • 封装就是将数据或者函数等集合在一个个的单元中(类)。封装的意义在于保护或者防止代码(数据)被破坏。
  • 继承实现重用代码,节省开发时间。子类可以继承父类的一些东西。
  • 多态是相同的操作、函数或过程,可以作用于多种类型的对象上并获得不同的结果。不同的对象,收到同一消息可以产生不同的结果。多态性将接口与实现分离。
  • C++的多态主要是通过虚函数实现的。那些被virtual关键字修饰的成员函数就是虚函数

虚函数表

虚函数的地址存放于虚函数表中,运行期多态就是通过虚函数和虚函数表实现的。类的对象内部有指向类内部的虚函数表地址的指针,通过这个指针调用虚函数。

C语言中存储机制 

  • 栈:存放程序临时创建的局部变量。
  • 堆:存放进程运行中被动态分配的内存段。
  • BSS段:用来存放程序中未初始化的全局变量和静态变量。不占用磁盘空间,只在运行时占用内存空间。
  • 数据段:存放程序中已初始化的全局变量和静态变量。
  • 代码段:存放程序执行代码的一块内存区域。

代码段,数据段和BSS段是编译的时候由编译器分配的,堆和栈是程序运行的时候系统分配的。

预编译(预处理)

代码文本的替换工作。处理#开头的指令,为编译做的预备工作的阶段。

  • #include指令:将xxx.xxx文件的全部内容插入此处。若用<>括起文件,则在 系统的include目录中寻找文件,若用""括起文件则在当前目录中寻找文件。
  • #define指令:定义标识,表示有效范围整个程序。定义常数。定义“函数”。定义“宏函数”。
  • #if,#else,#endif指令:

#if  definded(指令)  //如果定义了标识

要执行的指令

#else

要执行的指令

#endif

const常量有数据类型,而宏常量没有数据类型。编译器可对const进行类型安全检查,而对后者只进行简单的替换,可能会产生意想不到的错误。

数据库

mysql引擎

  • 概念:MySQL中的数据用不同的技术存储在文件(或内存)中。这些技术中的每一种技术都使用不同的存储机制、索引技巧、锁定水平并且最终提供广泛的不同的功能和能力。通过选用不同的技术,可以获得额外的速度或者功能,从而改善应用的整体功能。
  • MyISAM

不支持事务(事务是逻辑上的一组操作,组成这组操作的各个单元,要么全成功,要么全失败)

表级锁定(数据更新时锁定整个表):锁定的实现成本小但是降低了并发性能

读写互相阻塞:写入是阻塞读取,读取时阻塞写入,但读取不会阻塞另外的读取

只会缓存索引:缓存区只会缓存索引,不会缓存数据。

读取速度快,占用资源相对较少

不支持外键约束,但支持全文索引

  • InnoDB

支持事务

行级锁定(更新时一般是锁定当前行):通过索引实现

读写阻塞与实务隔离级别相关

具有非常高效的缓存特性:能缓存索引,也能缓存数据

支持外键约束

mysql的索引优化

  • 创建索引:对于查询占主要的应用来说,索引尤为重要。如果不添加索引,查找任何哪怕是一条特定的数据都会进行一次全表扫描。
  • 复合索引:将常用作限制条件的列放在最左边,依次递减。
  • 索引不会包含有NULL值的列
  • 使用短索引

B树和B+树的区别

  • 关键字的数量不同:B+树中分支节点有m个关键字,其叶子节点也有m个,其关键字只是起到了一个索引的作用,但是B树虽然也有m个子节点,但是只拥有m-1个关键字。
  • 存储的位置不同:B+树中的数据都存储在叶子节点上,其所有的叶子节点的数据组合起来就是完整的数据,但是 B树的数据存储在每一个节点中,不仅仅存在叶子节点上。
  • 分支节点的构造不同:B+树的分支节点仅仅存储着关键字的信息和儿子指针,内部节点仅仅包含存储信息。
  • 查询不同:B树在找到具体的数值以后结束,B+树则需要通过索引找到叶子节点中的数据才结束。

数据库的内连接和外连接

  • 内连接:连接结果仅包含符合连接条件的行,参与连接的两个表都应该符合连接条件
  • 外连接:连接结果不仅包含符合连接条件的行,同时也包含自身不符合条件的行,包括左外连接,右外连接和全外连接。

数据库的事务

  • 原子性:事务包含的所有操作要么全部成功,要么全部失败回滚。
  • 一致性:事务必须使数据库从一个一致性状态变道另一个一致性状态。
  • 隔离性:当多个用户并发访问数据库时,数据库为每个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。
  • 持久性:一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的。
  • 脏读:一个事物处理过程里读取了另一个未提交事务中的数据。
  • 不可重复读:对于数据库中的某个数据,一个事务范围内多次查询却返回了不同的数据值。这是由于在查询间隔,被另一个事务修改并提交了。
  • 虚读:事务非独立执行时发生的一种现象。

什么是SQL注入,如何解决

  • 所谓SQL注入式攻击,就是攻击者把SQL命令插入到web表单的输入域或者页面请求字符串 ,欺骗服务器执行恶意的sql命令。造成原因是因为程序没有有效过滤用户的输入,使攻击者成功的向服务器提交恶意的sql查询代码,程序在接收后错误的将攻击者的输入作为查询语句的一部分执行,导致原始的查询逻辑被改变。
  • 替换单引号:把所有单独出现的单引号改成两个单引号
  • 删除用户输入内容中的所有连字符
  • 对于用来执行查询的数据库账户,限制其权限。
  • 限制表单或查询字符串的输入长度
  • 将用户登陆名称、密码等数据加密保存
  • 检查提取数据的查询所返回的记录数量

设计模式

  • 单例模式:一个类始终只能创建一个实例,则这个类被称为单例类,这种模式被称为单例模式。
  • 工厂模式:工厂父类负责定义产品对象的公共接口,子类工厂负责创建具体的产品对象,把产品实例化。
  • 建造者模式:将一个复杂对象的构建与表示分离,使得同样的构建过程可以创建不同的表示。
  • 抽象工厂模式:提供一个创建一系列或相关依赖对象的接口,而无需指定它们的类。
  • 原型模式:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
  • 适配器模式:将一个类的接口变换成客户端所期待的另一个接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。
  • 代理模式:当客户端代码需要调用某个对象时,客户端并不关心是否准确得到该对象,只需要一个提供该功能的对象即可,此时我们可返回该对象的代理 。
  • 策略模式:定义一系列算法,把他们一个个封装起来,并且使他们可替换。
  • 观察者模式:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都得到通知并被自动更新。
  • 装饰者模式:动态地给一个对象添加一些额外的职责。

其他

从10亿个数中找到最大的100w个

  • 建堆   建堆的时间复杂度为O(100w  log 100w),算法的时间复杂度为(10亿*100w  log 100w)
  • 将10亿个数据分组存放,每个组中找出最大的100w个,再合并比较
  • 全部排序/局部淘汰/分治法/hash法(去重)/最小堆

测试用例的设计

  • 黑盒测试

等价类划分:把所有可能输入的数据,即程序的输入域划分成若干部分,然后从每一个子集中选取少量具有代表性的数据作为测试用例。有效等价类/无效等价类

边界值分析:对输入或者输出边界值进行测试。

错误推测法:基于经验和直觉推测程序中可能存在的各种错误,从而有针对性的设计测试用例。

  • 白盒测试

逻辑覆盖法

语句覆盖:被测程序的每一个语句至少执行一次

判定覆盖:程序中的每一个判断至少获得一次“真”和一次“假”

条件覆盖:判定中每个条件的所有可能结果至少出现一个

条件/判定覆盖:判定中每个条件的所有可能结果至少出现一次,并且每个判定本身的所有可能结果也至少出现一次。

条件组合覆盖:使每个判定中条件的各种组合都至少出现一次

基本路径测试法

程序的控制流图,程序环形复杂度

  • 接口测试

常见排序算法及其时间复杂度(时间/空间/稳定性)

  • 直接插入排序  O(n²)  O(1) 稳定
  • 冒泡排序         O(n²)  O(1)  稳定
  • 选择排序         O(n²)  O(1)   不稳定
  • 希尔排序                      O(1)     不稳定
  • 快速排序       O(nlogn) O(nlogn) 不稳定
  • 归并排序   O(nlogn) O(n)      稳定
  • 堆排序  O(nlogn) O(1)  不稳定
  • 基数排序    O(d*(n+r))  O(n+r) 稳定

算法部分

1. 数组里搜第k大的数(类似于快排分治法的思想)

2. 非递归二分查找

3.给你一个字符串数组,如何按照“同组异构”的规则进行最这些字符串进行分组

我理解的是:{“a”,“acb”,”bca”,”aabcd”,”abcdefgh”,”abdca”} 分组后:{“a”}一组,{“acb”,“bca”}一组,{“aabcd”,”abdca”}一组,{“abcdefgh”}一组。

4.二叉树两个节点的最近公共祖先

5.找出两个字符串中相同的片段

6.用递归的方法实现5743变成3475

猜你喜欢

转载自blog.csdn.net/qq_40769893/article/details/88367706
今日推荐