文章目录
- 前言
- 一、面试问题
- 1. 在浏览器url上写一个地址,请描述一下网络方面有哪些变化
- 2. 堆栈数据存储位置
- 3. HTTP POST请求支持的数据格式
- 4. 缩容要注意些什么?
- 5. Python中元组、数组、list和数组的区别
- 6. Python中is和==的区别
- 7. HTTP与HTTPS
- 8. 已知两个ip地址,它们是怎么实现传输的?(网络层及以下)
- 9. IO操作相关
- 10. 网络拥塞
- 11. HTTP常用状态码
- 12. Websocket是长连接还是短连接?
- 13. Session和token有啥区别?哪个安全?
- 14. 分析一下视频卡住的原因
- 15. 断点续传怎么实现?
- 16. socket通信过程
- 二、测试案例设计
- 1. 如何测试抖音软件
- 2. 将数据从一个库,一张表迁移到多个库多张表怎么测试
- 3. 发送红包测试用例
- 4. 微信朋友圈评论功能测试案例
- 三、手撕算法
- 1. 无重复字符的最长子串
- 2. 用两个栈实现队列
- 3. 找众数-Moore投票算法
- 4. 买卖股票的最佳时机
- 5. 求2的N次幂
- 6. 兔子繁殖问题
- 7. 删除链表的倒数第N个结点
- 8. 存在重复元素III
- 9. 最大数
- 10. 给10人随机分配100元红包,每人最低获取1元
- 总结
前言
面经收集于牛客网上,部分面试题时间可能有些久了,此处只是做个学习整理。大多数题目的回答来自于deepseek。
一、面试问题
1. 在浏览器url上写一个地址,请描述一下网络方面有哪些变化
- URL解析
- 协议解析:浏览器检查URL的协议(如
https://
),决定使用HTTPS(默认端口443)或HTTP(默认端口80)。 - 域名提取:从URL中提取主机名
- 协议解析:浏览器检查URL的协议(如
- DNS解析
- 浏览器缓存:首先检查本地缓存是否有域名对应的IP地址。
- 系统缓存:若浏览器无缓存,查询操作系统缓存。
- 递归查询:向配置的本地DNS服务器(如ISP提供的服务器)发起请求。若本地DNS无记录,则从根DNS(
.
)开始迭代查询 → 顶级域(.com
) → 权威DNS(example.com
的NS记录),最终获取IP地址。 - DNS结果缓存:将解析结果缓存到本地,加速后续访问。
- 建立TCP连接
- 三次握手
- 客户端发送
SYN
包到服务器IP的443端口。 - 服务器回复
SYN-ACK
。 - 客户端发送
ACK
,完成连接建立。
- 客户端发送
- TLS(HTTPS)
- 客户端发送
ClientHello
,包含支持的加密算法。 - 服务器回复
ServerHello
,选择加密算法并发送证书。 - 客户端验证证书(如CA机构签名),生成会话密钥,用证书公钥加密后发送。
- 服务器用私钥解密,双方启用加密通信。
- 客户端发送
- 三次握手
- 发送HTTP请求(请求行、请求头、请求体)
- 服务器处理请求(执行后端代码,生成响应)
- 接收HTTP响应(状态行、响应头、响应体)
- 浏览器渲染
- 连接终止
- 四次挥手(TCP关闭):
- 客户端发送
FIN
。 - 服务器回复
ACK
。 - 服务器发送
FIN
。 - 客户端回复
ACK
,连接关闭。
- 客户端发送
- 四次挥手(TCP关闭):
2. 堆栈数据存储位置
- 栈
- 存储位置:位于内存的高地址区域,通常向低地址方向增长。
- 存储内容:函数调用时的局部变量、函数参数、返回地址、寄存器上下文。
- 管理方式:由编译器自动分配和释放,遵循LIFO(后进先出)原则。
- 特点:分配和释放速度快、内存大小有限、内存连续、大小在编译时通常就确定了
- 堆
- 存储位置:位于内存的低地址区域,通常向高地址方向增长。
- 存储内容:动态分配的内存(malloc/new等)、全局变量、静态变量
- 管理方式:由程序员手动分配和释放(或由垃圾回收器管理)
- 特点:分配和释放速度相对较慢、内存大小受系统可用内存限制、内存可以不连续、大小在运行时动态确定
3. HTTP POST请求支持的数据格式
HTTP POST 请求可以支持多种数据格式,主要通过 Content-Type
请求头来指定。
- 表单数据(Form Data):
application/x-www-form-urlencoded
- 多部分表单数据(Multipart Form Data):
multipart/form-data
- JSON格式:
application/json
- 纯文本:
text/plain
- 二进制数据:
application/octet-stream
4. 缩容要注意些什么?
缩容(减少系统资源)是云计算和系统运维中的重要操作,需要注意以下关键点:
- 业务影响评估
- 确认缩容时段是否避开业务高峰期
- 评估可能对用户体验造成的影响
- 容量规划
- 确保剩余资源能满足当前业务需求
- 保留适当的缓冲资源应对突发流量
- 数据安全
- 缩容前做好数据备份
- 确保无单点故障产生
- 监控与告警
- 缩容后密切监控系统指标
- 准备好快速回滚方案
实施步骤建议:
- 先在非生产环境测试缩容方案
- 采用渐进式缩容而非一次性大幅缩减
- 记录缩容前后的性能指标对比
- 通知相关团队缩容计划
5. Python中元组、数组、list和数组的区别
-
数组和list的区别
特性 数组(array.array) 列表(List) 数据类型 同类型元素 可混合类型 性能 数值操作更快 更灵活但稍慢 内存 更节省内存 占用更多内存 内置支持 需要 import array
内置支持 -
列表 (List) vs 元组 (Tuple) vs 集合 (Set) vs 字典 (Dict)
特性 列表(List) 元组(Tuple) 集合(Set) 字典(Dict) 可变性 可变 不可变 可变 可变 有序性 有序 有序 无序 Python 3.7+ 有序 重复元素 允许 允许 不允许 键不允许,值允许 语法 [1, 2, 3]
(1, 2, 3)
{1, 2, 3}
{'a': 1, 'b': 2}
查找效率 O(n) O(n) O(1) 键查找O(1) 用途 有序数据集合 不可变数据集合 去重、成员测试 键值对映射 -
选择建议
- 需要有序且可变的集合 → 列表
- 需要不可变的有序集合 → 元组
- 需要快速成员测试或去重 → 集合
- 需要键值对映射 → 字典
- 需要两端高效操作 → deque
-
列表 (List) vs 元组 (Tuple)
特性 列表(List) 元组(Tuple) 可变性 可变 不可变 语法 [1, 2, 3]
(1, 2, 3)
性能 稍慢 更快 内存占用 较多 较少 使用场景 需要修改的数据集合 固定不变的数据集合
6. Python中is和==的区别
特性 | == | is |
---|---|---|
比较内容 | 值是否相等 | 是否是同一个对象 |
可重载性 | 可重载(__eq__ ) | 不可重载 |
执行速度 | 相对较慢 | 相对较快 |
适用场景 | 常规值比较 | 单例对象、对象身份验证 |
7. HTTP与HTTPS
-
区别
HTTP HTTPS 默认端口80 默认端口443 POST明文传输、数据未加密、安全性差 传输过程ssl加密、安全性较好 响应速度快、消耗资源少 响应速度较慢、消耗资源多、需要用到CA证书 -
HTTPS链接建立的过程
- 客户端发送
ClientHello
,包含支持的加密算法。 - 服务器回复
ServerHello
,选择加密算法并发送证书。 - 客户端验证证书(如CA机构签名),生成会话密钥,用证书公钥加密后发送。
- 服务器用私钥解密,双方启用加密通信。
- 客户端发送
-
对称加密和非对称加密
- 对称加密
- 单一密钥:加密和解密使用同一个密钥
- 高效快速:加密速度快,适合大数据量加密
- 算法举例:AES、DES
- 非对称加密:
- 密钥对:公钥(公开)和私钥(保密),相比对称加密速度较慢
- 数学关联:公钥私钥成对生成,有数学关系
- 算法举例:RSA、DSA
- 对称加密
8. 已知两个ip地址,它们是怎么实现传输的?(网络层及以下)
当两个IP地址之间进行通信时,在不考虑传输层及以上协议(如TCP/UDP)的情况下,数据通过以下机制实现传输:
- 路由决策
- 源主机查询路由表确定数据包下一跳
- 判断目标IP是否在同一子网
- ARP解析
- 同一子网:通过ARP获取目标MAC地址
- 不同子网:获取默认网关MAC地址
- 帧封装
- 物理传输
- 以太网:CSMA/CD(冲突检测)
- 交换机:MAC地址表转发
- 路由器:解封装->路由决策->重新封装
9. IO操作相关
-
什么是I/O操作?
IO操作(Input/Output Operation,输入/输出操作)是指计算机系统与外部世界或其他设备之间进行的数据交换过程。
主要类型:
-
输入操作(input):从外部设备(如键盘、鼠标、磁盘、网络等)读取数据到计算机内存中
例如:读取文件、接收网络数据、获取用户键盘输入
-
输出操作(output):将计算机内存中的数据写入到外部设备
例如:写入文件、发送网络数据、在屏幕上显示内容
常见IO形式:文件IO、网络IO、控制台IO、设备IO
技术特点:
- 速度差异:IO操作通常比CPU和内存操作慢得多
- 阻塞与非阻塞:
- 阻塞IO:操作完成前程序会暂停
- 非阻塞IO:操作立即返回,不等待完成
- 同步与异步:
- 同步IO:程序等待操作完成
- 异步IO:操作在后台进行,完成后通知程序
-
-
什么情况会使得IO变慢?为什么?
- 硬件限制:
- 存储介质速度:
- HDD机械硬盘(约80-160MB/s)比SSD(约200-550MB/s)慢得多
- 网络存储(NAS/SAN)通常比本地存储慢
- 接口带宽:
- USB 2.0(480Mbps)比USB 3.0(5Gbps)慢
- SATA III(6Gbps)比NVMe(PCIe 3.0 x4可达4GB/s)慢
- 设备老化:存储设备随着使用时间增长性能下降
- 存储介质速度:
- 系统资源竞争:
- CPU负载过高:无法及时处理I/O请求
- 内存不足:导致频繁的交换(Swap)操作
- 并发I/O过多:多个进程/线程同时访问同一设备
- 缓存未命中:需要直接从较慢的存储介质读取
- 软件因素:
- 小文件频繁读写:大量小文件操作比单个大文件慢
- 随机访问:HDD上随机读写比顺序读写慢100倍以上
- 缓冲区大小不当:过小的缓冲区增加I/O次数
- 同步写入策略:确保数据落盘的操作(如fsync)显著变慢
- 文件系统碎片:特别是FAT32/NTFS等传统文件系统
- 网络IO特定问题:
- 高延迟网络:跨国或卫星网络连接
- 带宽限制:网络带宽被其他应用占用
- 协议开销:如TCP握手、加密/解密过程
- 数据包丢失:导致重传和等待
- 其他因素:
- 虚拟化开销:在虚拟环境中I/O需要额外转换
- 安全扫描:实时病毒扫描等安全检查
- 日志记录:详细的I/O操作日志会增加开销
- 温度过高:可能导致设备自动降速
- 硬件限制:
-
什么是虚拟内存?
虚拟内存是计算机系统内存管理的一种技术,它通过将**物理内存(RAM)和磁盘存储(如硬盘或SSD)**结合起来,为程序提供一个比实际物理内存更大的地址空间。这使得每个程序可以认为自己拥有连续且独立的内存空间,而不必关心其他程序的内存使用情况。
-
IO频繁对虚拟内存有什么影响?为什么?主要影响:
- 加速交换活动:内存压力过大、换页增加、双重I/O负担
- 性能下降:系统花费更多时间在数据交换而非实际计算
- TLB(转换检测缓冲区)效率下降:频繁的页交换导致地址转换缓存失效
- 文件系统缓存受影响:频繁I/O占用缓存空间,挤出了可能更有用的数据
原因分析:
- 磁盘带宽竞争
- 数据流冲突:应用I/O和交换I/O共享同一磁盘通道
- 寻道时间增加:磁头需要在数据区和交换区间频繁移动(HDD尤其明显)
- 内存压力机制
- 脏页回写:文件I/O产生的脏页需要定期写回磁盘
- 回收算法负担:页面替换算法(如LRU)需要更频繁工作
- 上下文切换开销
- 进程调度:I/O等待导致更多上下文切换
- 缺页中断:触发更多缺页异常处理
- 写放大效应
- 元数据更新:每次I/O可能伴随inode、目录等元数据更新
- 日志开销:日志型文件系统需要额外写入日志记录
10. 网络拥塞
-
什么是网络拥堵
网络拥堵是指网络中的数据流量超过其承载能力,导致传输延迟增加、丢包率上升、吞吐量下降的现象。类似于交通堵塞,当太多设备同时发送数据时,网络设备(如路由器、交换机)无法及时处理,就会发生拥堵。
-
怎么解决?
- 增加带宽(扩容)
- 流量控制
- 优先级调度:保障关键业务(如视频会议)优先传输
- 限速(Rate Limiting):限制P2P下载、视频流媒体占用过多带宽
- 队列管理:采用先进队列算法(如FQ-CoDel)减少缓冲延迟
- 优化TCP/IP协议
- 使用BBR拥塞控制算法(Google开发,比传统CUBIC更高效)
- 调整TCP窗口大小,减少重传
- 负载均衡
- 使用多WAN口路由器,分流不同运营商的流量
- CDN(内容分发网络)就近提供数据,减少主干网压力
- 减少不必要的流量
- 启用数据压缩(如HTTP/2、Brotli)
- 缓存静态资源(如浏览器缓存、CDN缓存)
- 限制后台自动更新(如Windows Update、云同步)
-
什么情况会造成拥堵?
- 带宽不足
- 网络设备瓶颈
- 突发流量
- TCP/IP协议特性
- 网络拓扑设计不合理
-
什么是拥塞控制
拥塞控制是计算机网络中的一种关键机制,用于防止网络因数据流量过大而超载,确保网络高效、公平地运行。它主要应用于传输层协议(如TCP),但现代网络设备(如路由器)也会参与拥塞管理。
拥塞控制的目标是让发送方动态调整数据发送速率,使网络流量接近但不超出其承载能力(即“拥塞避免”)。
TCP拥塞控制的4个核心算法:
-
慢启动:发送方从极小的窗口(如1MSS)开始,指数级增长发送速率。
目的:快速探测可用带宽,避免一开始就冲击网络。
-
拥塞避免:当窗口达到阈值(
ssthresh
)后,改为每RTT(往返时间)增加1个MSS目的:接近网络容量时谨慎提速,避免引发拥塞。
-
快速重传:如果收到3个重复ACK,立即重传丢失的包(而不必等超时)
目的:减少因等待超时带来的延迟。
-
快速恢复:丢包后,窗口减半(而非重置为1),然后继续线性增长。
避免因单次丢包导致吞吐量骤降。
-
11. HTTP常用状态码
状态码 | 解释 |
---|---|
100 | Continue — 继续。客户端应继续其请求。 |
200 | OK — 请求成功。一般用于GET与POST请求。 |
301 | Moved Permanently — 永久重定向。 |
302 | Found — 暂时重定向。 |
400 | Bad Request — 客户端请求的语法错误,服务器无法理解。 |
403 | Forbideen — 服务器理解请求客户端的请求,但是拒绝执行此请求。 |
404 | Not Found — 服务器无法根据客户端的请求找到资源(网页)。 |
500 | Internal Server Error — 服务器内部错误,无法完成请求。 |
502 | Bad Gateway — 作为网关或者代理服务器尝试执行请求时,从远程服务器接收到了无效的响应。 |
12. Websocket是长连接还是短连接?
websocket是长连接。
关键区别:
短连接(如HTTP):
- 每次请求都需要建立新的 TCP 连接,请求完成后立即断开。
- 频繁的连接/断开开销大,不适合实时通信。
长连接(如websocket):
- 客户端和服务器通过一次 HTTP 握手升级为 WebSocket 协议后,保持 TCP 连接持久化。
- 连接建立后,双方可以随时双向通信(服务器可主动推送,客户端可随时发送消息)。
- 适合实时应用(聊天、游戏、股票行情等)。
WebSocket 的特点:
- 一次握手,长期复用:通过 HTTP 协议升级建立连接后,后续通信无需重复握手。
- 低延迟:避免了 HTTP 的多次连接开销。
- 双向通信:不同于 HTTP 的“请求-响应”模式,WebSocket 允许服务器主动推送数据。
13. Session和token有啥区别?哪个安全?
关键区别对比:
特性 | Session | Token(如JWT) |
---|---|---|
存储位置 | 服务端存储会话数据 | 客户端存储Token(无状态) |
通信方式 | 依赖Cookie | 通常通过Authorization 头 |
安全性风险 | CSRF(需额外防护) | Token 泄露(需 HTTPS+短期过期) |
扩展性 | 服务器集群需共享Session | 天然支持分布式 |
主动失效 | 服务端可立即注销 | 需额外实现黑名单或短期过期 |
Session更安全(如果安全防护):
- 服务端可控性强,能主动终止会话。
session_id
本身无意义,泄露后需进一步攻击(如窃取 Cookie)。
14. 分析一下视频卡住的原因
- 网络问题
- 带宽不足
- 网络抖动或丢包
- DNS或CDN问题
- 设备性能问题
- 硬件解码能力不足
- 内存或存储不足
- 视频编解码问题
- 编码格式不兼容
- 码率或帧率过高
- 服务端或播放器问题
- 服务端负载过高
- 播放器Bug或缓存策略
- 其他原因
- 协议或传输问题
- 防火墙/代理限制
解决方案:
- 网络问题:升级带宽、切换WiFi/有线、优化CDN。
- 设备问题:更换设备、启用硬解、清理内存。
- 编码问题:服务端提供多码率流(如HLS自适应)。
- 播放器问题:更新版本、调整缓冲策略(如ExoPlayer的
minBufferMs
)。
通过分层排查(网络→设备→编码→服务端),可快速定位问题根源。
15. 断点续传怎么实现?
断点续传(Resumable Upload/Download)允许文件传输中断后,从中断的位置继续传输,而不是重新开始。其核心实现依赖于分块传输、记录进度、校验一致性。以下是具体实现方案:
核心机制:
- 文件分块(Chunking)
- 将大文件分割成固定大小的小块(如每块1MB),逐个传输。
- 优势:避免单次传输失败导致全盘重试。
- 进度记录
- 客户端:记录已成功传输的块序号或字节位置(持久化到本地文件或数据库)。
- 服务端:保存已接收的块信息(如通过
Range
头或自定义元数据)。
- 唯一标识文件
- 使用文件哈希(如MD5/SHA1)或唯一ID标识文件,确保续传时文件未修改。
- 校验完整性
- 传输完成后校验整体文件哈希,确保分块合并后无错误
16. socket通信过程
- 服务器端(Server)
- Server->>Server: 1. 创建Socket (socket())
- Server->>Server: 2. 绑定IP和端口 (bind())
- Server->>Server: 3. 监听连接 (listen())
- Server->>Server: 4. 接受客户端连接 (accept())
- Server->>Client: 5. 读写数据 (read()/write())
- Server->>Server: 6. 关闭Socket (close())
- 客户端(Client)
- Client->>Client: 1. 创建Socket (socket())
- Client->>Server: 2. 连接服务器 (connect())
- Client->>Server: 3. 读写数据 (read()/write())
- Client->>Client: 4. 关闭Socket (close())
二、测试案例设计
1. 如何测试抖音软件
抖音作为一款短视频社交软件,测试需要覆盖功能、性能、兼容性、安全性和用户体验等多个方面。
- 功能测试
- 核心功能测试
- 视频播放测试
- 正常网络下视频自动播放
- 滑动切换视频的流畅性
- 暂停/继续播放功能
- 全屏/退出全屏模式
- 不同格式视频兼容性(MP4, MOV等)
- 用户交互测试
- 点赞、评论、收藏功能
- 分享功能(微信、QQ、微博等平台)
- 关注/取消关注用户
- @功能测试
- 私信功能
- 视频播放测试
- 拍摄与编辑功能
- 摄像头切换(前后置)
- 滤镜效果测试
- 音乐添加与同步
- 特效功能(慢动作、快进等)
- 视频剪辑功能
- 草稿保存与编辑
- 核心功能测试
- 性能测试
- 启动时间测试(冷启动、热启动)
- 视频加载时间(不同网络环境下)
- 内存占用测试
- CPU使用率测试
- 长时间使用后的性能表现
- 多任务切换测试
- 兼容性测试
- 设备兼容性
- 不同品牌手机(华为、小米、OPPO等)
- 不同屏幕分辨率
- 不同Android/iOS版本
- 网络兼容性
- 4G/5G/WiFi切换
- 弱网环境测试
- 网络中断恢复
- 设备兼容性
- 安全性测试
- 用户隐私保护
- 数据传输加密
- 敏感词过滤
- 举报功能有效性
- 未成年保护模式
- 用户体验测试
- 界面布局合理性
- 操作流畅度
- 新手引导有效性
- 错误提示友好性
- 无障碍功能测试
2. 将数据从一个库,一张表迁移到多个库多张表怎么测试
- 测试准备阶段
- 环境搭建
- 源环境:搭建与生产一致的源数据库环境
- 目标环境:准备多个目标数据库实例
- 中间件:如需使用分库分表中间件(如ShardingSphere、MyCat等),需一并部署
- 测试数据准备
- 全量数据:准备与生产数据量级相当的测试数据
- 包含各种边界条件的数据(如空值、极值、特殊字符等)
- 增量数据:模拟生产环境持续写入的场景
- 环境搭建
- 功能测试
- 数据完整性测试
- 源库数据总量验证
- 目标库数据总量验证
- 数据一致性测试
- 字段映射验证:检查每个字段是否正确迁移
- 数据类型校验:确保数据类型转换正确
- 分片规则验证:确保数据按预期规则分布到不同库表
- 特殊场景测试
- 主键冲突:测试重复主键的处理机制
- 空值处理:验证空值和NULL值的迁移情况
- 事务一致性:测试迁移过程中事务是否保持完整
- 数据完整性测试
- 性能测试
- 基准测试
- 全量迁移性能:测量完整迁移所需时间
- 增量同步性能:测试数据持续同步的延迟
- 压力测试
- 高并发写入:模拟生产环境写入压力
- 长时间运行:验证系统稳定性
- 资源监控
- CPU、内存、IO、网络等资源使用情况监控
- 数据库连接数监控
- 基准测试
- 异常测试
- 故障恢复测试
- 网络中断:模拟迁移过程中网络故障
- 数据库宕机:测试源库或目标库宕机场景
- 中间件故障:如使用中间件,测试其故障恢复能力
- 回滚测试
- 验证迁移失败后的回滚机制
- 检查回滚后数据的一致性
- 故障恢复测试
- 验证工具
- 数据比对工具(比如:数据比对脚本)
- 自动化测试框架
- 使用Jenkins等CI工具构建自动化测试流水线
- 编写自动化测试用例覆盖各种场景
- 上线前验证
- 影子测试:在不影响生产环境的情况下全量运行迁移程序
- 数据校验:对迁移结果进行全面校验
- 性能基准:记录各项性能指标作为生产参考
- 监控方案
- 迁移进度监控:实时显示迁移进度
- 数据一致性监控:定期校验源库和目标库数据
- 报警机制:设置异常情况自动报警
3. 发送红包测试用例
- 功能测试
- 红包金额测试
- 正常金额测试:发送0.01元、1元、100元、200元(假设200元为上限)
- 边界值测试:发送0元、0.009元(四舍五入应为0.01元)、200.01元
- 特殊金额测试:发送含小数的金额如8.88元、66.66元
- 超大金额测试:尝试发送超过上限的金额如1000元
- 红包个数测试
- 单个红包:发送给1个人
- 多个红包:发送给2-100人(假设100人为上限)
- 边界值测试:发送给0人、101人
- 特殊个数测试:发送幸运数字如6、8、9个
- 红包类型测试
- 普通红包:每个接收者金额相同
- 拼手气红包:金额随机分配
- 专属红包:指定给特定用户
- 支付方式测试
- 余额支付:账户余额充足/不足
- 银行卡支付:单卡/多卡支付
- 组合支付:余额+银行卡组合
- 红包金额测试
- 界面测试
- 红包金额输入框:只能输入数字和小数点
- 红包金额选择器:限制在合理范围内
- 红包主题/祝福语:长度限制、表情支持
- 发送按钮:金额不足时的提示
- 性能测试
- 高并发发送红包测试
- 大量用户同时抢红包测试
- 红包发送响应时间测试
- 安全测试
- XSS攻击测试:在祝福语中插入脚本
- SQL注入测试:在输入字段尝试SQL注入
- 金额篡改测试:拦截请求修改金额
- 重复发送测试:防止重复扣款
- 兼容性测试
- 不同操作系统:IOS、Android、鸿蒙
- 不同微信版本测试
- 不同屏幕尺寸适配
- 异常场景测试
- 发送过程中网络中断
- 支付密码错误
- 账户冻结状态下发送红包
- 红包过期未领取处理
- 数据校验测试
- 发送后账户余额正确扣除
- 接收方实际到账金额正确
- 红包记录在交易明细中正确显示
4. 微信朋友圈评论功能测试案例
- 功能测试
- 基础评论功能
- 发布一条朋友圈后,验证好友能否正常评论
- 验证评论字数限制(如最多140个字符)
- 验证评论中可包含的表情、图片、链接等特殊内容
- 验证评论后能否正常显示评论者的昵称和头像
- 验证评论时间的显示格式是否正确
- 评论交互功能
- 验证评论后原作者能否收到通知
- 验证评论后其他共同好友能否看到该评论
- 验证评论后的回复功能是否正常
- 验证评论后的点赞功能是否正常
- 验证评论后的删除功能是否正常
- 基础评论功能
- 权限测试
- 可见性权限
- 验证不同好友分组对朋友圈的评论权限(如部分可见、不给谁看)
- 验证陌生人(非好友)能否看到和参与评论
- 验证被拉黑用户能否看到和参与评论
- 验证朋友圈设置为"私密"时的评论权限
- 操作权限
- 验证非好友能否删除他人评论
- 验证朋友圈作者删除评论后,其他用户是否同步更新
- 验证朋友圈作者能否禁止某人对某条朋友圈评论
- 可见性权限
- 性能测试
- 负载测试
- 验证单条朋友圈下大量评论(如1000+)时的加载性能
- 验证短时间内高频评论时的系统响应
- 稳定性测试
- 验证长时间运行后评论功能的稳定性
- 验证网络不稳定时评论功能的容错性
- 负载测试
- 兼容性测试
- 设备兼容性
- 验证不同机型(iOS/Android)评论功能的兼容性
- 验证不同屏幕尺寸下的评论显示效果
- 版本兼容性
- 验证新老版本微信客户端间的评论功能兼容性
- 验证跨系统版本(如iOS 12与iOS 15)间的评论功能兼容性
- 设备兼容性
- 安全测试
- 内容安全
- 验证评论内容是否支持敏感词过滤
- 验证评论中是否禁止包含恶意链接或代码
- 数据安全
- 验证评论内容在传输过程中是否加密
- 验证删除评论后服务器数据是否同步清除
- 内容安全
- 异常测试
- 输入异常
- 验证输入超长评论时的处理机制
- 验证输入特殊字符(如emoji、HTML标签)时的处理
- 操作异常
- 验证断网状态下发表评论的提示信息
- 验证快速连续多次点击评论按钮的处理
- 输入异常
- 用户体验测试
- 交互体验
- 验证评论输入框的弹出和收起是否流畅
- 验证评论后的消息提示是否及时准确
- 视觉体验
- 验证评论列表的排版和显示效果
- 验证夜间模式下的评论显示效果
- 交互体验
- 国际化测试
- 多语言支持
- 验证不同语言环境下的评论功能
- 验证多语言混排评论的显示效果
- 时区支持
- 验证不同时区下评论时间的显示准确性
- 多语言支持
这类题目太多了,这边就只列举了四个。。。
三、手撕算法
1. 无重复字符的最长子串
// leetcode原题:给定一个字符串 s ,请你找出其中不含有重复字符的 最长 子串 的长度。
class Solution {
public int lengthOfLongestSubstring(String s) {
Map<Character, Integer> dic = new HashMap<>();
char[] ch = s.toCharArray();
int len = ch.length;
int i = -1, res = 0;
for(int j = 0; j < len; j++) {
if(dic.containsKey(ch[j])) {
i = Math.max(i, dic.get(ch[j]));
}
dic.put(ch[j], j);
res = Math.max(res, j - i);
}
return res;
}
}
2. 用两个栈实现队列
class MyQueue {
private Deque<Integer> queueA;
private Deque<Integer> queueB;
public MyQueue() {
queueA = new ArrayDeque<>();
queueB = new ArrayDeque<>();
}
public void push(int x) {
queueA.push(x);
}
public int pop() {
if (queueB.isEmpty()) {
while (!queueA.isEmpty()) {
queueB.push(queueA.pop());
}
}
return queueB.pop();
}
public int peek() {
if (queueB.isEmpty()) {
while (!queueA.isEmpty()) {
queueB.push(queueA.pop());
}
}
return queueB.peek();
}
public boolean empty() {
return queueA.isEmpty() && queueB.isEmpty();
}
}
3. 找众数-Moore投票算法
public class MajorityElement {
// 这种方法不需要使用额外的空间,只需要遍历数组两次。
// 第一次遍历找出候选众数,第二次遍历验证这个候选众数是否真的是众数。
public static int findMajorityElement(int[] nums) {
int count = 0;
Integer candidate = null;
for (int num : nums) {
if (count == 0) {
candidate = num;
}
count += (num == candidate) ? 1 : -1;
}
// 需要验证候选众数是否真的是众数(可选步骤,如果数组长度足够大,通常可以省省略)
count = 0;
for (int num : nums) {
if (num == candidate) {
count++;
}
}
if (count > nums.length / 2) { // 检查是否真的是众数
return candidate;
} else {
throw new IllegalArgumentException("No mahority element found");
}
}
}
4. 买卖股票的最佳时机
// 你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票
class Solution {
public int maxProfit(int[] prices) {
int maxProfit = 0;
int min = prices[0];
for (int i = 0; i < prices.length; i++) {
min = Math.min(min, prices[i]);
maxProfit = Math.max(maxProfit, prices[i] - min);
}
return maxProfit;
}
}
// 在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在 同一天 出售。
class Solution {
public int maxProfit(int[] prices) {
int profit = 0;
for (int i = 1; i < prices.length; i++) {
int tmp = prices[i] - prices[i - 1];
if (tmp > 0) {
profit += tmp;
}
}
return profit;
}
}
// 你可以无限次地完成交易,但是你每笔交易都需要付手续费。如果你已经购买了一个股票,在卖出它之前你就不能再继续购买股票了。
class Solution {
public int maxProfit(int[] prices, int fee) {
int n = prices.length;
int[][] dp = new int[n][2];
dp[0][0] = 0; // 手上没有股票
dp[0][1] = -prices[0]; // 手上有股票
for (int i = 1; i < n; i++) {
dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i] - fee);
dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] - prices[i]);
}
return dp[n - 1][0]; // 只有最后手里没有股票的时候,利润才有可能最大
}
}
// 优化
class Solution {
public int maxProfit(int[] prices, int fee) {
int n = prices.length;
int sell = 0; // 手上没有股票
int buy = -prices[0]; // 手上有股票
for (int i = 1; i < n; i++) {
sell = Math.max(sell, buy + prices[i] - fee);
buy = Math.max(buy, sell - prices[i]);
}
return sell; // 只有最后手里没有股票的时候,利润才有可能最大
}
}
5. 求2的N次幂
public class PowerOfNumberIterative {
public static void main(String[] args) {
double result = power(2, 10);
System.out.println(result);
}
// 迭代方法
public static double power(double x, int n) {
double result = 1.0;
long longN = Math.abs((long) n);
while (longN > 0) {
if ((longN & 1) != 0) { // 检查最低位是否为1(奇数情况)
result *= x; // 累乘底数x到结果中
}
x *= x; // 将底数平方以备下一次迭代使用(偶数情况)
longN >>= 1; // 将指数右移一位(等同于除以2)
}
return n < 0 ? 1 / result : result; // 处理负指数的情况并返回最终结果
}
// 递归方法
public static double power2(double x, int n) {
if (n == 0) {
return 1;
}
if (n < 0) {
return 1 / power2(x, -n);
}
// 递归
double half = power2(x, n / 2);
if (n % 2 == 0) {
return half * half;
} else {
return half * half * x;
}
}
}
6. 兔子繁殖问题
// 假设一对刚出生的兔子,从第三个月开始每个月都能生一对新兔子,新出生的兔子也是从第三个月开始繁殖。假设兔子不会死亡,问第n个月时有多少对兔子?
// 这个问题实际上就是著名的斐波那契数列(Fibonacci sequence):
// 第1个月:1对兔子(刚出生)
// 第2个月:1对兔子(还没成熟)
// 第3个月:2对兔子(原来的1对+新生的1对)
// 第4个月:3对兔子(原来的1对+上个月新生的1对又生1对)
// 第5个月:5对兔子
// ...
// 递推关系:f(n) = f(n-1) + f(n-2)
// 递归解法
public class RabbitRecursion {
/**
* 递归解决
* 时间复杂度:O(2^n) - 指数级
* 空间复杂度:O(n) - 调用栈深度
*/
public static int fibonacciRecursive(int n) {
if (n == 1 || n == 2) {
return 1;
}
return fibonacciRecursive(n - 1) + fibonacciRecursive(n - 2);
}
}
public class RabbitDP {
/**
* 动态规划解法(使用数组)
* 时间复杂度(O(n))
* 空间复杂度(O(n))
*/
public static int fibonacciDP(int n) {
if (n == 1 || n == 2) {
return 1;
}
int[] dp = new int[n + 1];
dp[1] = 1;
dp[2] = 1;
for (int i = 3; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
/**
* 空间优化的动态规划解法
* 时间复杂度:O(n)
* 空间复杂度:O(1)
*/
public static int fibonacciDPOptimized(int n) {
if (n == 1 || n == 2) {
return 1;
}
int prev = 1;
int curr = 1;
for (int i = 3; i <= n; i++) {
int sum = prev + curr;
prev = curr;
curr = sum;
}
return curr;
}
}
7. 删除链表的倒数第N个结点
// leetcode原题:给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummy = new ListNode(0, head);
ListNode first = head;
ListNode second = dummy;
for (int i = 0; i < n; i++) {
first = first.next;
}
while (first != null) {
second = second.next;
first = first.next;
}
second.next = second.next.next;
ListNode res = dummy.next;
return res;
}
}
8. 存在重复元素III
// 给你一个整数数组 nums 和两个整数 indexDiff 和 valueDiff 。
// 找出满足下述条件的下标对 (i, j):
// i != j,
// abs(i - j) <= indexDiff
// abs(nums[i] - nums[j]) <= valueDiff
// 如果存在,返回 true ;否则,返回 false。
class Solution {
public boolean containsNearbyAlmostDuplicate(int[] nums, int indexDiff, int valueDiff) {
TreeSet<Long> set = new TreeSet<>();
for (int i = 0; i < nums.length; i++) {
Long num = (long) nums[i];
// 找到比nums[i]小的最大元素
Long floor = set.floor(num);
if (floor != null && num - floor <= valueDiff) {
return true;
}
// 找到比nums[i]大的最小元素
Long ceiling = set.ceiling(num);
if (ceiling != null && ceiling - num <= valueDiff) {
return true;
}
set.add(num);
// 维护滑动窗口大小不超过indexDiff
if (i >= indexDiff) {
set.remove((long) nums[i - indexDiff]);
}
}
return false;
}
}
9. 最大数
// 给定一组非负整数 nums,重新排列每个数的顺序(每个数不可拆分)使之组成一个最大的整数。
class Solution {
public String largestNumber(int[] nums) {
// 1. 将 int 数组转换为 String 数组
String[] numsStr = new String[nums.length];
for (int i = 0; i < nums.length; i++) {
numsStr[i] = String.valueOf(nums[i]);
}
// 2. 自定义排序规则:比较 ab 和 ba 的大小
Comparator<String> comparator = new Comparator<String>() {
@Override
public int compare(String a, String b) {
String ab = a + b;
String ba = b + a;
return ba.compareTo(ab); // 降序排列(ba > ab时,b排在前面)
}
};
// 3. 排序
Arrays.sort(numsStr, comparator);
// 4. 处理前导零,应该返回0
if (numsStr[0].equals("0")) {
return "0";
}
// 5. 拼接结果
StringBuilder sb = new StringBuilder();
for (String num : numsStr) {
sb.append(num);
}
return sb.toString();
}
}
// 时间复杂度:O(n log n)(排序主导,n 是数组长度)。
// 空间复杂度:O(n)(存储字符串数组)。
10. 给10人随机分配100元红包,每人最低获取1元
public class RedPacket {
public static int[] allocate(int totalAmount, int numPeople, int minAmount) {
if (totalAmount < numPeople * minAmount) {
throw new IllegalArgumentException("总金额不足以满足每人最低金额");
}
int remaining = totalAmount - numPeople * minAmount;
int[] dividers = new int[numPeople - 1];
Random random = new Random();
// 生成numPeople - 1个随机隔板
for (int i = 0; i < dividers.length; i++) {
dividers[i] = random.nextInt(remaining + 1);
}
Arrays.sort(dividers);
// 计算每个人的分配金额
int[] result = new int[numPeople];
int prev = 0;
for (int i = 0; i < dividers.length; i++) {
result[i] = dividers[i] - prev + minAmount;
prev = dividers[i];
}
result[numPeople - 1] = remaining - prev + minAmount;
return result;
}
public static void main(String[] args) {
int[] amounts = allocate(100, 10, 1);
System.out.println("分配结果:" + Arrays.toString(amounts));
System.out.println("总和:" + Arrays.stream(amounts).sum()); // 验证总和
}
}
总结
面试算法leetcode是王道,多练习总归会是有用的吧。。。