企业概况
公司简介
华为技术有限公司成立于 1987 年,总部位于深圳,业务覆盖运营商网络、企业业务、终端、云计算与数字能源,是全球领先的 ICT 基础设施与智能终端提供商。OD(Outsourcing Dispatch)是华为与人力服务商合作的外包用工模式,由服务商签约、在华为项目组内工作,考核方式与自有员工接近,但合同主体与晋升通道不同。OD 的 Java 岗位一般落在业务系统开发与内部平台建设上。
- 成立时间:1987 年
- 总部:深圳
- 主营业务:ICT 基础设施、智能终端、云计算与数字能源
- 规模与行业地位:2024 年销售收入 8621 亿元人民币,全球通信设备第一梯队
- 研发与专利:2024 年研发费用 1797 亿元,占收入 20.8%
岗位定位
OD 的 Java 开发岗以业务系统交付为主:接需求、做设计、写代码、联调上线,与自有员工的日常工作几乎一致。区别在劳动关系与晋升体系——由服务商签约,绩效达标且有名额时才有转正机会。对候选人来说,这类岗位的价值在于能进入体量更大、流程更规范的研发体系。
- 所属体系:各产品线的项目组,按业务线分配
- 核心职责:业务系统开发、接口设计、线上问题定位
- 发展路径:OD 开发 → 高级 OD 开发 → 绩效达标后申请转正
- 培养体系:导师制 + 项目实战,考核以交付质量与协作评价为主
OD 流程前置有在线性格测试,HR 会提示答题倾向,但不要答得过于极端。技术一面的八股密度高、覆盖面广,是主要的分水岭。手撕必须用目标岗位的语言,投 Java 岗就用 Java 写。
招聘概况
OD 招聘全年滚动,流程比校招短:简历筛选 → 性格测试 → 资面 → 技术一面 → 技术二面 → 主管面 → offer。性格测试与资面属于筛选环节,技术面才是真正的分水岭,一般两轮技术面当天或隔天完成。
- 招聘规模:按项目组缺口滚动招聘
- 面向专业:计算机、软件工程等相关专业,本科及以上
- 工作地点:深圳、东莞、西安、南京、成都等
- 投递窗口:全年开放
企业概况整理自公司、公开年报与招聘公告,具体数据以官方最新发布为准。
面试总体概述
案例背景
这位同学走的是华为 OD 通道,流程从在线性格测试开始,通过后是资面,再到技术一面与技术二面。技术一面是典型的高密度八股,覆盖 HashMap、并发、锁、缓存、数据库隔离级别,最后手撕三数之和;技术二面则完全转向项目,逐条问「为什么这么实现」并追加限制条件,最后手撕力扣 809。主管面尚未进行。
基本信息
- 公司:华为技术有限公司
- 岗位:OD · Java 开发
- 用工形式 / 部门:OD(外包)
- 面试轮次:2 轮
- 平均时长:52 分钟
- 题目数量:共 20 题,均附参考答案
- 整体难度:★★★☆☆ 中等
- 候选人背景:经历 Java 后端,具备项目开发经验
- 是否参加笔试:是
- 笔试形式:在线性格测评
高频考点
| 考点 | 分类 | 出现轮次 | 次数 |
| --- | --- | --- | --- |
| HashMap 底层与负载因子 | 基础八股 · 集合 | 技术一面 | 2 |
| 并发与锁(AQS / CAS / 锁分类) | 基础八股 · 并发 | 技术一面 | 3 |
| 包装类型与 equals / hashCode | 基础八股 · 语言 | 技术一面 | 3 |
| 缓存三大问题 | 基础八股 · 缓存 | 技术一面 | 1 |
| MySQL 事务与隔离级别 | 基础八股 · 数据库 | 技术一面 | 2 |
| 项目设计决策与方案演进 | 项目经历 | 技术二面 | 3 |
| 手撕(双指针 / 字符串分组) | 算法 | 技术一面、技术二面 | 2 |
备考建议
| 优先级 | 易失分点 | 准备动作 |
| --- | --- | --- |
| P0 | 八股覆盖广但都答不深,容易在并发与集合上失分 | 按「HashMap → 并发(AQS/CAS/锁)→ 集合对比 → 缓存 → 数据库」五条线各整理一页纸,每条能讲 2 分钟 |
| P0 | 包装类型比较用 == 导致代码出错 | 牢记 Integer 缓存池范围,包装类型一律用 equals 或先拆箱;把这条写进自己的编码检查清单 |
| P1 | 项目只会讲实现,答不出「为什么不选另一个方案」 | 每个关键决策做一张 A/B 对比卡:两个方案的收益、代价、适用条件 |
| P1 | 手撕时用错语言或忽略去重细节 | 按目标岗位语言刷 10 道高频题,重点练去重、边界与自测流程 |
面试流程
流程总览
| # | 环节 | 形式 | 时长 | 筛选重点 |
| --- | --- | --- | --- | --- |
| 1 | 简历投递 | 招聘平台 / 服务商渠道 | 1~2 天 | 年限与技术栈匹配 |
| 2 | 性格测试 | 在线测评 | 约 30 分钟 | 性格倾向与岗位匹配 |
| 3 | 资面 | 电话 / 视频 | 约 20 分钟 | 履历真实性、期望薪资、稳定性 |
| 4 | 技术一面 | 视频面试 | 约 60 分钟 | Java 八股广度 + 一道手撕 |
| 5 | 技术二面 | 视频面试 | 约 45 分钟 | 项目深度、方案权衡、一道手撕 |
| 6 | 主管面 | 视频面试 | 约 30 分钟 | 稳定性、协作、对 OD 的认知 |
各环节说明
1. 简历投递
由服务商的招聘顾问对接,核对年限、期望薪资与到岗时间后推给业务侧。
- 简历突出 Java 技术栈与项目量级
2. 性格测试
对接的 HR 通常会提示答题倾向(比如稳定性、抗压、团队配合相关的维度要往积极方向答)。测试在工作日提交后一般 1~2 个工作日出结果。
- 前后答案保持一致,别在同类题上自相矛盾
- 不要选极端选项,除非确实符合
3. 资面
问校园或工作经历、遇到的最大困难怎么解决、期望薪资、换城市能否适应这类问题。HR 会提示回答方向,当天就能出结果。
- 期望薪资给一个区间,并说明依据
- 换城市的问题要提前想清楚,不要临场犹豫
4. 技术一面
面试官会顺着简历问项目,然后铺开一轮八股:集合、并发、锁、缓存、数据库,每个方向都有涉及但不深入,最后给一道力扣题。
- 八股按「结构—原理—使用场景」三段准备
- 手撕用目标岗位语言写,别用自己顺手的其他语言
5. 技术二面
纯项目深挖,会顺着你的实现往深里推:为什么这么设计、有没有更优方案、加限制条件后怎么做,最后一道字符串处理题。反馈很快,通常十分钟内出结果。
- 每个技术选型准备一个被淘汰的替代方案
- 把「加限制条件」当成常规追问来准备
6. 主管面
本次记录中该环节尚未进行。按 OD 的常规节奏,主管面会关注离职原因、加班接受度、对 OD 身份的认知与长期打算。
- 提前想清 OD 的接受边界与转正预期
- 准备一个能体现主动性的项目故事
第 1 轮:技术一面
面试形式与时长
- 轮次名称:技术一面
- 面试类型:技术面
- 时长:60 分钟
- 形式:视频面试
- 本轮题量:16 题
重点考查维度
- 基础八股 —— 本组 15 道题
- 算法手撕 —— 本组 1 道题
八股密度很高,几乎每个方向都会碰一下:集合、并发、锁、缓存、数据库、语言基础。面试官对深度要求不算苛刻,但要能连续答下来不卡壳。手撕给的是力扣原题,本地 IDE 或共享编辑器都可能。
Q1 讲一下 HashMap 的底层原理。
基础八股 · 集合
参考答案
从结构、定位、冲突、扩容四点讲。
- 结构:数组 + 链表 + 红黑树。JDK 8 起链表长度到 8 且数组长度不小于 64 时转红黑树,退化到 6 再转回链表。
- 定位:key 的 hashCode 经扰动(高 16 位与低 16 位异或)后,用 (n-1) & hash 定位桶,等价于取模但更快(n 是 2 的幂)。
- 冲突:同桶元素先比 hash 再比 equals,都用 == 优先比较引用,避免不必要的 equals 调用。
- 扩容:默认容量 16、阈值 12,元素数超阈值翻倍。JDK 8 用高低位拆分迁移(hash & oldCap 判断放原位置还是原位置+oldCap),不再重算 hash,也不再有 1.7 头插法成环的问题。
- 线程安全:HashMap 并发 put 会丢数据甚至死循环(1.7),并发场景用 ConcurrentHashMap。
HashMap 红黑树扩容
Q2 HashMap 的负载因子为什么是 0.75?
基础八股 · 集合
参考答案
本质是「空间与冲突概率的折中」,不是一个随意取值。
- 定义:负载因子 = 元素数 / 桶数组长度,0.75 意味着装到 3/4 就扩容。
- 冲突与性能:因子过高,桶内链表变长,查询从 O(1) 退化;因子过低,扩容频繁,空间浪费大。
- 数学依据:官方注释里给了泊松分布的分析——在 0.75 的负载下,桶内元素个数服从参数约 0.5 的泊松分布,链表长度达到 8 的概率约为千万分之六,这也是树化阈值取 8 的原因。
- 工程权衡:0.75 让平均查找长度与空间开销都比较均衡,实践中几乎不需要改。
追问预警:如果明确知道元素数量,可以在构造时指定初始容量(预估数量 / 0.75 + 1),避免多次扩容。
负载因子泊松分布
Q3 实现线程的方式有哪几种?
基础八股 · 并发
参考答案
标准说法是四种,实际开发常用的是后两种。
- 继承 Thread:重写 run,简单但占用了唯一的继承位,不推荐。
- 实现 Runnable:把任务与线程解耦,无返回值、不能抛受检异常。
- 实现 Callable:有返回值、可以抛异常,配合 FutureTask 或线程池使用。
- 使用线程池(ExecutorService):工程上的标准做法,复用线程、控制并发数、统一异常处理。
补充:严格来说只有「继承 Thread」和「实现 Runnable」是创建线程的方式,Callable 与线程池都是 Runnable 的包装。
追问预警:为什么不推荐 new Thread()——无法复用、无界创建会打爆内存,且缺少统一的生命周期管理。
线程 Callable 线程池
Q4 Callable 接口为什么有返回值?它的实现原理是什么?
基础八股 · 并发
参考答案
关键是「返回值怎么跨线程传回来」。
- 定义:Callable 只有一个 V call() throws Exception,允许返回值和抛出受检异常,弥补 Runnable 只能 void run() 的不足。
- 原理:Callable 本身不直接跑在线程里,要包一层 FutureTask。FutureTask 实现 Runnable,内部持有 Callable、一个 state 状态位和 outcome 结果字段。
- 流程:run() 里调用 call(),把结果存进 outcome,把 state 从 NEW 改为 COMPLETING 再改为 NORMAL;调用 get() 时若未完成就挂到 waiters 队列上等待,完成后被唤醒并返回 outcome。
- 异常:call 抛出的异常会被捕获并存入 outcome,get 时以 ExecutionException 形式抛出,原始异常在 cause 里。
- 好处:把「异步执行」与「结果获取」统一成一套模型,这也是线程池 submit 能返回 Future 的原因。
Callable FutureTask 异步
Q5 讲一下 AQS 的原理
基础八股 · 并发
参考答案
AQS(AbstractQueuedSynchronizer)是 Java 并发包的基石,ReentrantLock、Semaphore、CountDownLatch 都基于它。
核心三件套:一个 volatile int state(同步状态)、一个 FIFO 双向等待队列(CLH 变体)、以及 Condition 条件队列。
- 获取:tryAcquire 由子类实现(ReentrantLock 里就是 CAS 把 state 从 0 改成 1,重入则 state+1),失败则把当前线程包装成 Node 入队并 park。
- 释放:tryRelease 成功后,唤醒队首的后继节点,让它重新竞争。
- 两种模式:独占(Exclusive)与共享(Shared),CountDownLatch 用共享模式实现多个线程同时等待。
- 公平与非公平:公平锁入队前先检查有没有前驱,非公平锁直接抢,吞吐更高但可能饿死。
- 为什么高效:CAS 自旋 + park/unpark 阻塞,避免了无谓的 CPU 空转;队列操作只在必要时加锁。
AQS state CLH 队列
Q6 讲一下 CAS 是什么,有什么问题?
基础八股 · 并发
参考答案
CAS(Compare And Swap)是一条 CPU 原子指令,包含三个操作数:内存地址 V、期望值 A、新值 B。仅当 V 的当前值等于 A 时才把 V 更新为 B,否则失败重试。
Java 实现:Unsafe.compareAndSwapInt,底层是 cmpxchg 指令,多核下会加 lock 前缀保证原子性与可见性。
优点:无锁、不阻塞线程、没有上下文切换开销,适合竞争不激烈的场景(AtomicInteger 计数)。
- 问题一 ABA:值从 A 改成 B 又改回 A,CAS 察觉不到。解法是加版本号(AtomicStampedReference)。
- 问题二 自旋开销:竞争激烈时大量失败重试,CPU 空转。解法是限制自旋次数、退化为加锁,或参考 LongAdder 的分段累加。
- 问题三 只能保证单个变量:多变量要封装成对象用 AtomicReference。
CAS ABA 原子类
Q7 缓存三剑客是什么?分别怎么解决?
基础八股 · 缓存
参考答案
指缓存穿透、缓存击穿、缓存雪崩三个典型问题。
- 穿透:查询的 key 在缓存和数据库里都不存在,请求全部打到数据库。解法是缓存空值(设短过期时间)或用布隆过滤器提前拦截。
- 击穿:某个热点 key 过期瞬间,大量并发同时打到数据库。解法是加互斥锁(只让一个线程回源)、逻辑过期(不设物理过期,异步刷新)、或热点数据永不过期。
- 雪崩:大量 key 在同一时刻集体过期,或缓存服务整体宕机。解法是过期时间加随机抖动、多级缓存、缓存集群高可用、以及降级限流兜底。
补充两个常被一起问的:
- 双写一致性:常用「先更新数据库再删缓存」,配合延迟双删或 binlog 订阅补偿。
- 热 key 与大 key:本地缓存 + 拆分,避免单节点压力倾斜。
穿透 击穿 雪崩
Q8 Java 的基本数据类型有哪些?
基础八股 · 语言基础
参考答案
八种,按是否带小数分两类。
- 整数:byte(1 字节,-128~127)、short(2 字节)、int(4 字节)、long(8 字节)。
- 小数:float(4 字节,单精度)、double(8 字节,双精度)。
- 字符:char(2 字节,无符号,存 Unicode 码元)。
- 布尔:boolean(JVM 规范未规定大小,实际常按 1 字节或 4 字节处理)。
对比要点:
- 包装类型(Integer/Long/Double…)是对象,可以参与泛型、可以为 null,但比较必须用 equals。
- 自动装箱有缓存:Integer 默认缓存 -128~127,所以这个区间内 == 成立。
- 金额用 BigDecimal,不要用 double——二进制浮点无法精确表示 0.1。
基本类型 装箱 BigDecimal
Q9 HashSet 的底层原理是什么?
基础八股 · 集合
参考答案
一句话:HashSet 内部就是一个 HashMap,元素作为 key,value 是一个固定的占位对象(PRESENT)。
- 添加:add(e) 实际调用 map.put(e, PRESENT),返回值为 null 表示新增成功,非 null 表示元素已存在。
- 去重:靠 HashMap 的 key 唯一性——先比 hash,再用 equals 判断,所以自定义对象必须同时重写 hashCode 与 equals,只重写一个会出现「重复元素加不进去」或「不同对象被判为重复」。
- 无序:存取顺序与哈希分布有关,不保证插入顺序;要保序用 LinkedHashSet,要排序用 TreeSet。
- 复杂度:平均 O(1),最坏(大量冲突退化为链表)O(n)。
- 非线程安全:并发场景用 Collections.newSetFromMap(new ConcurrentHashMap<>())。
HashSet HashMap equals
Q10 ArrayList 和 LinkedList 有什么区别?
基础八股 · 集合
参考答案
从底层结构推到使用场景。
- 结构:ArrayList 是动态数组,内存连续;LinkedList 是双向链表,每个节点额外存前后指针。
- 随机访问:ArrayList O(1);LinkedList O(n),必须从头或尾遍历。
- 增删:ArrayList 在尾部追加均摊 O(1),中间插入要移动元素 O(n);LinkedList 定位到位置本身是 O(n),定位后改指针 O(1),所以「LinkedList 增删快」只在已有节点引用时成立。
- 内存:ArrayList 有扩容时的数组拷贝与空间预留;LinkedList 每个节点有额外指针开销,且节点分散不利于 CPU 缓存。
- 实践建议:绝大多数场景用 ArrayList,即使频繁在头部操作;需要队列语义用 ArrayDeque,它比 LinkedList 更快。
ArrayList LinkedList ArrayDeque
Q11 Java 里的锁有哪几种?它们有什么区别?
基础八股 · 并发
参考答案
按不同维度分类,别混着讲。
- 按是否阻塞:悲观锁(先加锁再操作,如 synchronized、ReentrantLock)与乐观锁(CAS 或版本号,冲突时重试)。
- 按特性:公平锁 / 非公平锁(能否插队)、可重入锁 / 不可重入锁、独享锁 / 共享锁(读锁是共享)、分段锁(ConcurrentHashMap 1.7 的思想)。
- 按实现:synchronized(JVM 层面,monitor 实现,1.6 后有偏向锁、轻量级锁、重量级锁的升级)、ReentrantLock(AQS 实现,可中断、可超时、可多条件)、读写锁 ReentrantReadWriteLock、StampedLock(乐观读)。
- 选择建议:简单同步用 synchronized(JVM 会优化);需要可中断、超时、公平或条件变量时用 ReentrantLock;读多写少用读写锁。
追问预警:synchronized 的锁升级过程与对象头 Mark Word 的关系。
synchronized ReentrantLock 锁升级
Q12 Integer 为什么小于 128 的数用 == 比较结果是一致的?
基础八股 · 装箱
参考答案
因为 Integer 内部有缓存池。
- 机制:Integer.valueOf(int) 在 -128~127 范围内返回 IntegerCache.cache[i + 128] 这个预先创建好的对象,超出范围才 new Integer(i)。
- 所以两个 100 的 Integer 指向同一个缓存对象,== 比较引用自然为 true;而 200 会各自 new,== 为 false。
- 为什么是 -128~127:这是 byte 的取值范围,覆盖了最常见的计数场景;上限可用 -XX:AutoBoxCacheMax 调整。
- 正确做法:包装类型比较值一律用 equals,或用 Objects.equals;也可以先拆箱成基本类型再比。
- 更隐蔽的坑:Integer a = 100; Integer b = 100; a == b 为 true,但 new Integer(100) == new Integer(100) 永远为 false。
这道题在实际编码里也是高频坑,三数之和对撞去重时就栽在这里。
IntegerCache 装箱 ==
Q13 equals 和 == 有什么区别?
基础八股 · 语言基础
参考答案
- ==:对基本类型比的是值,对引用类型比的是「是否指向同一个对象」(地址)。
- equals:Object 的默认实现就是 ==,但 String、Integer 等类重写成了比内容。所以能不能比值,取决于有没有重写。
- 契约:重写 equals 必须同时重写 hashCode,否则放进 HashMap/HashSet 会出问题——两个 equals 相等的对象 hashCode 不同,会被分到不同的桶。
- 五个约定:自反、对称、传递、一致、与 null 比较返回 false。
- 实践:比较字符串用 Objects.equals(a, b),或把常量写在前面(如 "abc".equals(x))避免 NPE;枚举用 == 更安全。
追问预警:为什么重写 equals 一定要重写 hashCode——反例是两个内容相同的对象 hashCode 不同,map.get 拿不到值。
equals hashCode ==
Q14 MySQL 的 ACID 是什么?分别由什么保证?
基础八股 · 数据库
参考答案
四个特性对应四种机制。
- 原子性(A):事务里的操作要么全成功要么全回滚。由 undo log 保证——修改前的旧值记在 undo log 里,失败时反向执行。
- 一致性(C):事务前后数据满足业务约束。这是目的而不是手段,由 A、I、D 加上业务约束共同保证。
- 隔离性(I):并发事务之间互不干扰。由锁(行锁、间隙锁)与 MVCC(版本链 + ReadView)共同实现。
- 持久性(D):提交后即使断电也不丢。由 redo log + WAL 机制保证——先写日志再刷数据页,崩溃后用 redo 重放。
追问预警:redo log 与 binlog 的两阶段提交(prepare → commit),目的是保证两份日志一致,避免主从数据不一致。
ACID undo log redo log
Q15 MySQL 的事务隔离级别有哪几种?分别解决了什么问题?
基础八股 · 数据库
参考答案
四种,按隔离强度递增。
- 读未提交(Read Uncommitted):能读到其他事务未提交的数据,会脏读,几乎不用。
- 读已提交(Read Committed):只能读已提交的数据,解决脏读,但同一事务内两次读可能不同(不可重复读)。Oracle、PostgreSQL 的默认级别。
- 可重复读(Repeatable Read):同一事务内多次读结果一致,解决不可重复读;MySQL 的默认级别,通过 MVCC 的 ReadView 实现。
- 串行化(Serializable):加锁串行执行,解决幻读,但并发性能最差。
关键点:MySQL 的 RR 级别在「快照读」下靠 MVCC 避免幻读,「当前读」下靠间隙锁(Next-Key Lock)避免幻读,所以严格说 RR 也能防幻读,这是 InnoDB 与标准定义的差异。
追问预警:MVCC 的 ReadView 里四个字段(m_ids、min_trx_id、max_trx_id、creator_trx_id)怎么判断可见性。
隔离级别 MVCC 幻读
Q16 手撕:三数之和
算法手撕 · 排序 + 双指针
参考答案
题目:给定数组 nums,找出所有和为 0 且不重复的三元组。
思路:排序 + 固定第一个数 + 双指针。排序后可以跳过重复元素,也保证了双指针收缩的方向性。
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
Arrays.sort(nums);
List<List<Integer>> res = new ArrayList<>();
for (int i = 0; i < nums.length - 2; i++) {
if (nums[i] > 0) break; // 最小的都大于 0,后面不可能有解
if (i > 0 && nums[i] == nums[i - 1]) continue; // 跳过重复的第一个数
int l = i + 1, r = nums.length - 1;
while (l < r) {
int sum = nums[i] + nums[l] + nums[r];
if (sum == 0) {
res.add(Arrays.asList(nums[i], nums[l], nums[r]));
while (l < r && nums[l] == nums[l + 1]) l++;
while (l < r && nums[r] == nums[r - 1]) r--;
l++;
r--;
} else if (sum < 0) {
l++;
} else {
r--;
}
}
}
return res;
}
}
易错点:① 跳过重复元素时用 `nums[i] == nums[i - 1]` 而不是 `nums[i] == nums[i + 1]`;② 双指针移动时注意边界 `l < r`;③ 判断 `nums[i] > 0` 而不是 `>= 0`;④ 如果用 List 存结果并用 indexOf 去重,注意 Integer 比较必须用 equals,不能用 ==。
复杂度:O(n²) 时间,O(1) 额外空间(不含结果集)。
本题最容易翻车的不是算法,而是用 == 比较 Integer 导致的去重失效。
排序 双指针 去重
五、第 2 轮:技术二面
5. 1 面试形式与时长
| 项目 | 内容 |
| --- | --- |
| 轮次名称 | 技术二面 |
| 面试类型 | 技术面 |
| 时长 | 45 分钟 |
| 形式 | 视频面试 |
| 本轮题量 | 4 题 |
5. 2 重点考查维度
- 项目经历 —— 本组 3 道题
- 算法手撕 —— 本组 1 道题
纯项目,一条一条往下问,会追加限制条件看你怎么应变。八股一个都没问,直接进入手撕环节。反馈速度极快,面完十分钟内出结果。
5. 3 面试真题与参考答案
Q1 项目逐条深挖:你当时为什么这么实现?
项目经历 · 设计决策
参考答案
二面的问法是「顺着实现反推决策」,每一层都要有一句理由。
- 先给约束:当时的并发量、数据量、交付时间、团队技术栈,理由必须挂在约束上。
- 再给选择:为什么用这张表/这个缓存/这个中间件,而不是另一个。
- 给代价:这个选择带来了什么问题(一致性变弱、运维复杂),后来怎么补的。
- 给边界:什么情况下这个方案会失效,届时的演进方向是什么。
面试官常追问「如果当时选另一个会怎样」,准备时把每个关键决策做成 A/B 对比,是最有效的办法。
提醒:不要把所有决策都说成自己拍的板,团队约束讲清楚反而更可信。
Q2 有没有更好的优化方案?如果加一些限制条件,你会怎么做?
项目经历 · 方案演进
参考答案
这类问题考的是「方案的可延展性」,按三层答。
- 现状层:当前方案的瓶颈在哪(数据库压力、接口串行、缓存命中率低)。
- 优化层:给 2~3 个方向并排序——加缓存、异步化、读写分离、分库分表、批量化。每个方向说清收益与改动成本。
- 限制层:面试官加的限制通常是「不能加机器」「不能改表结构」「延迟要压到 50ms」,这时答案要随之收敛,比如只能做本地缓存 + 结果合并。
- 收尾:给出你的推荐方案与理由,说明为什么现在不做其他方案。
一个加分细节:主动提「优化的前提是先有度量」,说清你会先看哪些指标再动手。
Q3 项目中你遇到的最大困难是什么?怎么解决的?
项目经历 · 技术难点
参考答案
选「有约束、有取舍、有结果」的难点,避免纯知识型问题。
- 难点举例:数据迁移不能停机、缓存与库不一致、消息重复消费导致对账不平。
- 约束:数据量多大、可接受的不一致窗口、团队人力与时间。
- 方案:先定位根因,再给分层防护(本地消息表 + 定时补偿 + 对账兜底),最后灰度验证。
- 结果:问题率从多少降到多少,是否有返工。
- 沉淀:把这次的经验做成了什么规范或工具。
结尾补一句「难的不是方案,是让所有人都按同一个约定实现」,把技术问题落到协作上,二面主管很吃这一套。
Q4 手撕:力扣 809. 情感丰富的文字
算法手撕 · 双指针分组
参考答案
题目:给定字符串 s 和单词数组 words,统计 words 中有多少个单词可以由 s「扩张」得到。扩张规则是:把连续相同字符的一组(长度 n)扩成不少于 3 个且不等于原长度的任意长度,即新长度 m 满足 m ≥ 3 且 m ≠ n;若 n = 3(可以扩到 b,且 b ≠ a 自动满足);a > b 不合法。
def expressiveWords(s, words):
def group(t):
res = []
i = 0
while i < len(t):
j = i
while j < len(t) and t[j] == t[i]:
j += 1
res.append((t[i], j - i))
i = j
return res
gs = group(s)
ans = 0
for w in words:
gw = group(w)
if len(gw) != len(gs):
continue
ok = True
for (a, ca), (b, cb) in zip(gs, gw):
if a != b or ca < cb or (ca > cb and ca < 3):
ok = False
break
ans += 1 if ok else 0
return ans
验证:s 为 heeellooo、words 为 ["hello","hi","helo"] 时返回 1(只有 hello 能扩成 heeellooo)。
边界:空字符串、单字符组、group 数量不等直接判不合法。复杂度 O(len(s) + 所有单词长度之和)。
分组 双指针 字符串
六、注意事项
OD 的技术面难度中等,真正拉开差距的是八股的连贯性和手撕的语言选择。
01 手撕必须用岗位语言
投 Java 岗就用 Java 写。日常用 C++ 或 Python 刷题的人,现场用 Java 会写得很别扭,尤其在集合与类型比较上容易出错。
02 包装类型比较是高频坑
三数之和对撞去重、缓存 key 比较、结果去重,只要用到 == 比 Integer 就可能出错。统一改用 equals 或先拆箱,并在自测时专门验证一次。
03 八股要能连续答下来
一面每个方向都会问,答一句就卡住很伤。按「结构—原理—场景」三段背,保证每道题都有 2 分钟的存量。
04 负载因子这类「为什么」题别只背结论
0.75 要能扯到空间与冲突的折中、泊松分布、以及树化阈值取 8 的原因,只答「官方推荐」会被追问到没话说。
05 性格测试不要前后矛盾
HR 会提示倾向,但同一维度反复出现时答案要一致。选得过于极端或自相矛盾,可能在技术面之前就被筛掉。
06 二面别只讲实现
面试官一定会问「有没有更好的方案」「加个限制你会怎么做」。准备时把每个决策的替代方案与代价写下来,别临场编。
07 资面问题提前定口径
期望薪资、换城市、到岗时间这三件事在资面就会被问到,口径定了就不要在后续环节改口。
总结收尾
这份记录的价值在于把 OD 的完整链路摊开了:性格测试与资面是筛人,技术一面是刷八股广度,技术二面是验项目真实性。一面的八股看似零散,其实集中在五条线上——集合、并发、包装类型与 equals、缓存、数据库事务,按线准备比按题准备效率高得多。二面则要习惯「加限制条件」的追问方式,随时能给出退一步的可行方案。最后提醒一句:手撕选语言这件事,现场真的会决定成败。
以上内容整理自学员和应届同学反馈的真实面经,结合华为技术有限公司的最新考情整理,仅供参考学习。祝各位求职者顺利拿到心仪 offer!