大厂真题 / 虾皮
虾皮 5.9 笔试真题 - 研发岗
本场考试概述
考试时间:2026年5月9日 考试岗位:通用研发岗 难度评级:中等
考点分析:
- 第一题:反向贪心 + 不变量推理(难度中等偏难)
- 第二题:中心扩展(难度简单)
- 第三题:0/1 背包计数 DP(难度中等)
建议策略:
- 第一题先想清楚”差值必须是初始差的 $2^k$ 倍”这个不变量,再从目标倒推回起点
- 第二题直接中心扩展 $O(n^2)$ 即可通过,注意去重和按位置排序
- 第三题是标准 0/1 背包计数,注意空集方案数初始化为 1
第一题:最少计算次数
题目描述
给定两个数 $(x, y)$,允许两种操作:
- 同时加 1:$(x, y) \to (x+1, y+1)$
- 同时乘 2:$(x, y) \to (2x, 2y)$
求将 $(x, y)$ 转换成 $(X, Y)$ 的最少操作次数。如果不能转换,返回 $-1$。
样例
输入
10,100,22,202
输出
2
思路分析
第一步:正向搜索的困难
正向操作有两个分支(加 1 或乘 2),状态空间随步数指数膨胀。但反向操作是确定的:乘 2 的逆是同时除 2(要求两数都为偶),加 1 的逆是同时减 1。
第二步:不变量分析
差值 $Y - X$ 在加 1 操作下不变,在乘 2 操作下翻倍。因此从 $(x,y)$ 经若干次操作到达 $(X,Y)$,差值关系为 $(Y - X) = (y - x) \times 2^k$。这是可达的必要条件。
第三步:反向贪心策略
从 $(X, Y)$ 倒推回 $(x, y)$:
- 能除 2 就除 2(让差值减半,最快对齐)
- 两数任一为奇时无法除 2,必须先减 1 凑齐偶数
- 当差值已对齐($Y - X = y - x$)时,剩余只用减 1 操作即可
题解代码
import sys
input = sys.stdin.readline
def solve(x, y, X, Y):
if X < x or Y < y:
return -1
ans = 0
while X != x or Y != y:
d = Y - X
d0 = y - x
if d == d0:
if d == 0:
while X > x:
if X % 2 == 0 and X // 2 >= x:
X //= 2
else:
X -= 1
ans += 1
return ans
return ans + (X - x)
if (d > 0) != (d0 > 0) and d != 0 and d0 != 0:
return -1
if (d == 0) != (d0 == 0):
return -1
if abs(d) <= abs(d0):
return -1
if X % 2 != 0 or Y % 2 != 0:
if X - 1 < x or Y - 1 < y:
return -1
X -= 1
Y -= 1
ans += 1
else:
nX, nY = X // 2, Y // 2
if nX < x or nY < y:
return -1
X, Y = nX, nY
ans += 1
return ans
line = input().strip()
parts = line.split(',')
x, y, X, Y = int(parts[0]), int(parts[1]), int(parts[2]), int(parts[3])
print(solve(x, y, X, Y))
复杂度分析
时间复杂度:$O(\log V)$,$V$ 为数值范围,每次除 2 让数值减半 空间复杂度:$O(1)$
第二题:所有最长回文子串
题目描述
给定字符串 $s$,找出其中所有长度最大的回文子串。如果存在多个不同的最长回文子串则全部输出,按首次出现位置升序,相同子串只输出一次。
样例
输入
"babad"
输出
["bab","aba"]
思路分析
第一步:为什么用中心扩展
最长回文子串是经典题。暴力枚举子串 $O(n^3)$ 太慢,但中心扩展法 $O(n^2)$ 对 $n \leq 1000$ 完全够用。每个回文都有唯一的对称中心,枚举中心向两边扩展即可。
第二步:枚举所有中心
长度 $n$ 的字符串有 $2n - 1$ 个潜在中心:$n$ 个字符位置(奇数长度回文)和 $n-1$ 个间隔位置(偶数长度回文)。
第三步:维护最优集合
用变量记录当前最大长度,遇到更长的清空旧结果,长度持平且内容未见过则追加。最后按首次出现位置排序输出。
题解代码
import sys
input = sys.stdin.readline
line = input().strip()
s = line.strip('"')
n = len(s)
best_len = 0
positions = []
seen = set()
for center in range(2 * n - 1):
if center % 2 == 0:
l = r = center // 2
else:
l = center // 2
r = l + 1
while l >= 0 and r < n and s[l] == s[r]:
l -= 1
r += 1
l += 1
r -= 1
if r < l:
continue
cur_len = r - l + 1
if cur_len > best_len:
best_len = cur_len
positions = [l]
seen = {s[l:r + 1]}
elif cur_len == best_len:
sub = s[l:r + 1]
if sub not in seen:
positions.append(l)
seen.add(sub)
positions.sort()
parts = ['"' + s[i:i + best_len] + '"' for i in positions]
print('[' + ','.join(parts) + ']')
复杂度分析
时间复杂度:$O(n^2)$ 空间复杂度:$O(n)$
第三题:组合个数
题目描述
给定整数 $m$ 和 $n$,从 $1$ 到 $m$ 的整数中选出若干个互不相同的整数,使其和等于 $n$。求所有满足条件的组合个数。
样例
输入
6 8
输出
4
思路分析
第一步:识别问题模型
从 ${1, 2, \ldots, m}$ 中选若干不重复的数凑出和为 $n$。每个数最多用一次——这就是标准的 0/1 背包计数问题。
第二步:状态定义与转移
定义 $f[i][j]$ = 用 ${1, \ldots, i}$ 凑出和为 $j$ 的方案数。
- 不取 $i$:$f[i][j] = f[i-1][j]$
- 取 $i$(要求 $j \geq i$):$f[i][j] += f[i-1][j-i]$
初始化 $f[0][0] = 1$(空集是合法方案),其余为 0。
第三步:答案
$f[m][n]$ 即为所求。可以用滚动数组优化空间到 $O(n)$,但 $O(mn)$ 也完全够用。
题解代码
import sys
input = sys.stdin.readline
m, n = map(int, input().split())
f = [[0] * (n + 1) for _ in range(m + 1)]
f[0][0] = 1
for i in range(1, m + 1):
for j in range(n + 1):
f[i][j] = f[i - 1][j]
if j >= i:
f[i][j] += f[i - 1][j - i]
print(f[m][n])
复杂度分析
时间复杂度:$O(m \cdot n)$ 空间复杂度:$O(m \cdot n)$,可优化为 $O(n)$
小结
- 第一题考察逆向思维和不变量分析,反向操作消除了搜索的分支爆炸问题
- 第二题是回文子串的经典中心扩展法,注意去重和输出格式
- 第三题是 0/1 背包计数的直接应用,关键是初始化 $f[0][0] = 1$