最近,Microsoft 的 Codility OA 正在大量发放中。
很多同学看到题目后的第一反应是:"稳了,这不就是 DP/贪心 吗?"
千万别大意。
如果你只把它当成普通的算法题写,哪怕 Bug Free,大概率也只能拿个 Rej。
我们 oavoservice 团队第一时间拿到了真题,发现这里面埋了无数个 Engineering Judgment(工程判断力) 的深坑。
🔍 为什么 Microsoft Codility 的题"阴"?
Microsoft 不像 Google/Meta 那样喜欢出 LeetCode Hard,但它有个更恶心的特点:
✅ 题目描述很长,业务场景复杂
✅ 需要自己处理输入输出
❌ 边界条件极其刁钻
❌ DP 状态定义稍有偏差,全盘皆输
而且 Codility 平台的特点是:
- 没有交互式 Debugger
- 提交后才能看到哪些用例挂了
- 一旦 TLE,你根本不知道是哪个用例超时
这种"盲打"模式,对心态的考验极大。
昨天我们有个 UIUC 的学员,第一题写了 40 分钟,提交后发现只过了 60%,当场就慌了。如果不是我们的 Senior 导师实时在语音里稳住他:"别急,这是 DP 状态转移出了问题,重新定义状态!",他大概率第二题都写不完。
最后结果?110分钟,2道题,全部 All Passed。 稳稳拿下面试门票。
今天把这套热乎的真题拆解一下,告诉你们坑都在哪。
💻 真题一:多米诺骨牌序列(Domino Sequence)
🔍 题目核心
给定长度为 2*N 的数组 A,表示 N 个多米诺骨牌。每个骨牌由两个数字组成:
- 第 K 个骨牌的左右数字分别是
A[2*K]和A[2*K+1]
正确的多米诺序列:相邻骨牌的接触部分数字必须相同。
题目要求:最少需要移除多少个骨牌,使剩余骨牌形成正确序列?
限制:
- 不允许重新排序或旋转骨牌
- N 范围:[1..50,000]
- 每个元素范围:[1..6]
示例解析
Example 1:
A = [2, 4, 1, 3, 4, 6, 2, 4, 1, 6]
骨牌:(2,4), (1,3), (4,6), (2,4), (1,6)
- (2,4) 和 (1,3):右边 4 != 左边 1,不连接
- (2,4) 和 (4,6):右边 4 == 左边 4,可以连接!
- 最优解:保留 (2,4), (4,6),移除其他 3 个
答案:3
🤯 为什么这题挂了一大片?
绝大多数人看到这题,第一反应是:
❌ "这不就是最长连续子序列吗?"
❌ "用贪心,能连就连!"
❌ "暴力枚举所有可能!"
然后越写越乱,最后发现根本处理不了:
- 如何定义"可连接"?
- 如何处理多个可能的起点?
- 如何保证找到全局最优解?
✅ 正确的工程思维
这是一个**「最长递增子序列」的变种 + DP」**问题。
关键洞察
这不是"贪心",而是"动态规划"!
- 每个骨牌都可以作为起点
- 对于每个骨牌,需要找到前面所有能连接的骨牌
- 状态转移:
dp[i] = max(dp[j] + 1),其中j < i且骨牌j的右边等于骨牌i的左边
💣 隐形坑点
坑点 1:数组索引转换
# 错误写法
left = A[i] # ❌ 这是什么?
# 正确写法
left = A[2*i] # 第 i 个骨牌的左边
right = A[2*i + 1] # 第 i 个骨牌的右边
坑点 2:边界条件
- 只有 1 个骨牌:答案是 0(不需要移除)
- 所有骨牌都不相连:答案是 N-1(保留任意 1 个)
坑点 3:DP 初始化
- 每个骨牌单独成序列时,长度都是 1
- 不能初始化为 0
🎯 满分实现(Python)
def solution(A):
if len(A) == 0:
return 0
n = len(A) // 2
if n == 1:
return 0
# 构建骨牌列表
dominoes = []
for i in range(n):
left = A[2 * i]
right = A[2 * i + 1]
dominoes.append((left, right))
# DP: dp[i] 表示以第 i 个骨牌结尾的最长序列长度
dp = [1] * n
for i in range(1, n):
for j in range(i):
# 如果第 j 个骨牌的右边等于第 i 个骨牌的左边
if dominoes[j][1] == dominoes[i][0]:
dp[i] = max(dp[i], dp[j] + 1)
# 最长序列的长度
max_keep = max(dp)
# 需要移除的数量
return n - max_keep
⚡️ 时间复杂度优化
上面的解法是 O(n²),对于 N=50,000 可能会 TLE。
优化思路:用 HashMap 记录每个右值对应的最长序列
def solution(A):
if len(A) == 0:
return 0
n = len(A) // 2
if n == 1:
return 0
# HashMap: right_value -> max_length
max_len = {}
overall_max = 1
for i in range(n):
left = A[2 * i]
right = A[2 * i + 1]
# 查找能连接到当前骨牌左边的最长序列
current_len = max_len.get(left, 0) + 1
# 更新以 right 结尾的最长序列
max_len[right] = max(max_len.get(right, 0), current_len)
overall_max = max(overall_max, current_len)
return n - overall_max
时间复杂度:O(n)
空间复杂度:O(1) (因为骨牌数字只有 1-6)
🔢 真题二:三个非相邻元素的最大和(Maximum Sum of Three Non-Adjacent Elements)
🔍 题目核心
给定整数数组 A,选择恰好 3 个非相邻元素,使其和最大。
非相邻定义:三个元素的索引必须满足任意两个索引的差 > 1。
限制:
- N 范围:[5..100,000]
- 元素范围:[-100,000,000..100,000,000]
示例解析
Example 1:
A = [8, -4, -7, -5, -5, -4, 8, 8]
- 位置 0, 3, 6:8 + (-5) + 8 = 11
- 位置 0, 5, 7:8 + (-4) + 8 = 12 ✅ 最优
- 位置 0, 6, 7:8 + 8 + 8 = 24,但 6 和 7 相邻 ❌
答案:12
Example 2:
A = [-2, -8, 1, 5, -8, 4, 7, 6]
- 位置 3, 5, 7:5 + 4 + 6 = 15 ✅
答案:15
Example 3(负数情况):
A = [-3, 0, -6, -7, -9, -5, -2, -6]
- 位置 1, 3, 6:0 + (-7) + (-2) = -9 ✅
答案:-9
🤯 隐形杀招:为什么 90% 的人会挂?
看似简单的题目,实际上有三个致命陷阱:
陷阱 1:暴力枚举会 TLE
# ❌ 这样写必挂
max_sum = float('-inf')
for i in range(n):
for j in range(i+2, n):
for k in range(j+2, n):
max_sum = max(max_sum, A[i] + A[j] + A[k])
时间复杂度:O(n³),对于 N=100,000 必然超时。
陷阱 2:贪心选 3 个最大值会错
# ❌ 错误思路
sorted_indices = sorted(range(n), key=lambda i: A[i], reverse=True)
# 然后试图找 3 个不相邻的?
反例:
A = [10, 1, 1, 1, 1, 1, 9, 1, 1, 1, 1, 1, 8]
- 最大的 3 个:10, 9, 8
- 但位置:0, 6, 12
- 0 和 6 相差 6 > 1,6 和 12 相差 6 > 1 ✅
- 看起来可行?但如果是 [10, 1, 9, 1, 8] 呢?
贪心在有负数时完全失效!
陷阱 3:DP 状态定义不清晰
很多人想用 DP,但不知道状态怎么定义:
# ❌ 错误状态
dp[i] = "前 i 个元素的最大和" # 选了几个?
✅ 正确的工程思维:枚举中间元素 + 预处理
核心洞察
固定中间元素,问题变简单!
- 枚举第二个元素的位置
j(中间那个) - 在
[0, j-2]中找最大值(第一个元素) - 在
[j+2, n-1]中找最大值(第三个元素)
关键优化:预处理前缀/后缀最大值
💣 最容易翻车的地方
边界条件:
- 数组长度至少为 5(3 个元素 + 2 个间隔)
- 中间元素的范围:
[2, n-3]
负数处理:
- 不能跳过负数!即使都是负数,也必须选 3 个
🎯 满分实现(Python)
def solution(A):
n = len(A)
# 边界检查
if n < 5:
return float('-inf') # 不可能有解
# 预处理:prefix_max[i] 表示 [0, i] 的最大值
prefix_max = [float('-inf')] * n
prefix_max[0] = A[0]
for i in range(1, n):
prefix_max[i] = max(prefix_max[i-1], A[i])
# 预处理:suffix_max[i] 表示 [i, n-1] 的最大值
suffix_max = [float('-inf')] * n
suffix_max[n-1] = A[n-1]
for i in range(n-2, -1, -1):
suffix_max[i] = max(suffix_max[i+1], A[i])
# 枚举中间元素
max_sum = float('-inf')
for j in range(2, n-2):
# 第一个元素从 [0, j-2] 中选
first = prefix_max[j-2]
# 第三个元素从 [j+2, n-1] 中选
third = suffix_max[j+2]
current_sum = first + A[j] + third
max_sum = max(max_sum, current_sum)
return max_sum
时间复杂度:O(n)
空间复杂度:O(n)
⚡️ 空间优化版本
如果面试官追问"能否 O(1) 空间":
def solution(A):
n = len(A)
if n < 5:
return float('-inf')
max_sum = float('-inf')
for j in range(2, n-2):
# 实时计算左边最大值
left_max = max(A[0:j-1])
# 实时计算右边最大值
right_max = max(A[j+2:])
max_sum = max(max_sum, left_max + A[j] + right_max)
return max_sum
但这样会退化到 O(n²),不推荐!面试时先问清楚 trade-off。
💡 说句现实的:Microsoft Codility OA,真的不适合裸考
Microsoft SDE 的 TC(北美):
New Grad:$160k – $180k
SDE II:$200k – $250k
而 Codility OA 的残酷现实是:
❌ 输入输出需要自己处理
❌ 没有交互式调试
❌ 一个边界条件错误 = 全挂
❌ 一次机会,可能等半年
我们在 oavoservice 做的事情,其实很简单:
✅ 你写代码
✅ 有人盯状态定义
✅ 有人盯边界条件
✅ 有人盯时间复杂度
✅ 在你要 TLE / 写歪之前,直接叫停
不是你不行,是一个人上场风险太高。
🚀 oavoservice:你的满分通关专家
面对 Microsoft 这种 工程量大、细节层层递进 的 Codility OA,你需要的不只是一份答案,而是一个专业的 技术团队 支持。
我们提供:
✅ Codility 平台全覆盖:熟悉平台特性,规避隐藏陷阱
✅ 代码符合工业级标准:Production-Level Code,不是刷题风格
✅ 实时语音助攻:OA 进行时,资深工程师实时指导
✅ 满分保障:不过不收费,我们对自己的实力有信心
不要让一道 DP 状态转移、一个边界条件,卡住你通往 $200k Offer 的路。
We consistently provide professional online assessment services for major tech companies like Microsoft, Google, and Amazon, guaranteeing perfect scores.
👉 立即添加微信:Coding0201
锁定你的 Microsoft 面试机会!