大厂真题 / OPPO
OPPO 8.8 笔试真题 - 算法岗
本场考试概述
考试时间:2026年8月8日
考试岗位:算法岗
考试方向:编程题
难度评级:中等偏简单
本页收录考点:
- 第一题:字符串、线性扫描、端点选择(难度简单)
- 第二题:绝对值拆分、数学变形、极值预处理(难度中等)
建议策略:
- 第一题不要枚举子串。合法性只取决于首尾字符,因此最靠前和最靠后的幻觉字符一定构成最优区间
- 第二题先把绝对值拆成两个一次式,再把与核心位置无关的两个最大值预处理出来
- 两题均包含多组数据,且所有测试数据的总规模可达 $2\times 10^5$,应采用线性算法和快速读入
第 1 题:幻觉字符串
题目描述
将小写字母 o、大写字母 O 和数字 0 视为同一个字符,并把这三个字符统称为幻觉字符。
如果一个字符串以幻觉字符开头且以幻觉字符结尾,那么称它为幻觉字符串。字符串中间的字符没有任何限制;长度为 $1$ 的幻觉字符也同时满足开头和结尾条件。
给定一个由大小写字母和数字组成的字符串 $s$,求它的所有连续子串中,最长幻觉字符串的长度。如果不存在幻觉字符串,输出 $0$。
输入描述
第一行输入一个整数 $T$,表示测试数据组数,其中 $1\le T\le 2\times 10^5$。
每组测试数据包含两行:
- 第一行输入一个整数 $n$,表示字符串长度,其中 $1\le n\le 10^5$
- 第二行输入一个长度为 $n$ 的字符串 $s$,字符串仅由小写字母、大写字母和数字组成
保证单个测试文件中所有测试数据的 $n$ 之和不超过 $2\times 10^5$。
输出描述
对于每组测试数据,输出一行一个整数,表示最长幻觉字符串的长度;若不存在则输出 $0$。
样例
输入
3
1
o
10
helloW0rld
10
aob0cOdoOo
输出
1
3
9
样例解释
- 第一组中,
o本身就是幻觉字符串,长度为 $1$ - 第二组中,幻觉字符只出现在第 $5$ 位
o和第 $7$ 位0,最长合法子串为oW0,长度为 $3$ - 第三组中,最靠前的幻觉字符位于第 $2$ 位,最靠后的幻觉字符位于第 $10$ 位,取子串
ob0cOdoOo,长度为 $10-2+1=9$
思路分析
一个子串是否合法,只取决于它的第一个字符和最后一个字符是否属于集合 {o, O, 0},中间字符完全不受限制。
设所有幻觉字符在原串中的下标集合为 $S$,下标从 $0$ 开始。若 $S$ 为空,显然无解,答案为 $0$。否则记:
\[l=\min S,\qquad r=\max S.\]任意合法子串 $s[i\ldots j]$ 的两个端点都必须是幻觉字符,因此 $i,j\in S$,必有:
\[i\ge l,\qquad j\le r.\]所以它的长度满足:
\[j-i+1\le r-l+1.\]另一方面,$s[l\ldots r]$ 的首尾恰好都是幻觉字符,因此它本身合法,并且长度正好达到这个上界。故只需在线性扫描中找到第一个和最后一个幻觉字符,答案就是 $r-l+1$。
正确性证明
下面证明算法输出的是最长幻觉字符串的长度。
若原串中没有幻觉字符,则任何非空子串都无法以幻觉字符开头并结尾,算法输出 $0$,结论正确。
若原串中存在幻觉字符,设最靠前和最靠后的幻觉字符下标分别为 $l$ 和 $r$:
- 可行性:$s[l]$ 和 $s[r]$ 都是幻觉字符,因此子串 $s[l\ldots r]$ 是幻觉字符串,长度为 $r-l+1$
- 最优性:对任意幻觉子串 $s[i\ldots j]$,其首尾下标都对应幻觉字符。由 $l$、$r$ 的定义,有 $l\le i\le j\le r$,所以 $j-i+1\le r-l+1$
算法构造出了长度为 $r-l+1$ 的合法子串,同时任何合法子串都不可能更长,因此算法输出的答案最优。
题解代码
import sys
MAGIC = {ord("o"), ord("O"), ord("0")}
def solve() -> None:
data = sys.stdin.buffer.read().split()
if not data:
return
t = int(data[0])
pos = 1
answers = []
for _ in range(t):
n = int(data[pos])
s = data[pos + 1]
pos += 2
first = -1
last = -1
for i, ch in enumerate(s):
if ch in MAGIC:
if first == -1:
first = i
last = i
answers.append("0" if first == -1 else str(last - first + 1))
sys.stdout.write("\n".join(answers))
if __name__ == "__main__":
solve()
复杂度分析
设所有测试数据的字符串长度之和为 $N$。
时间复杂度:$O(N)$,每个字符只被扫描一次。
空间复杂度:算法本身的额外空间为 $O(1)$;若计入一次性读入的输入和输出缓冲区,则为 $O(N+T)$。
易错点
- 幻觉字符恰好是小写字母
o、大写字母O和数字0,不要混淆字母与数字 - 中间字符没有限制,不需要判断子串内部是否全部为幻觉字符
- 只有一个幻觉字符时,它本身就是长度为 $1$ 的合法子串
- 完全没有幻觉字符时应输出 $0$,不能用未初始化的左右端点计算长度
- 不要枚举所有子串或所有端点对,否则最坏会达到 $O(n^2)$
第 2 题:防风核心
题目描述
在数轴的整数坐标 $1,2,\ldots,n$ 上依次种植着 $n$ 棵树苗,第 $i$ 棵树苗至少需要 $a_i$ 的防风值才能存活。
你可以且仅能在某个整数坐标 $p$ 建立一个能量为非负整数 $P$ 的防风核心,其中 $1\le p\le n$。核心对坐标 $i$ 处树苗提供的防风值为:
\[P-\lvert i-p\rvert.\]为了使所有树苗存活,需要对每个 $1\le i\le n$ 满足:
\[P-\lvert i-p\rvert\ge a_i.\]等价地,当核心放在 $p$ 时,能量至少为:
\[P\ge \max_{1\le i\le n}\left(a_i+\lvert i-p\rvert\right).\]请最优地选择放置位置 $p$,求能够保护所有树苗存活的最小核心能量 $P$。
输入描述
第一行输入一个整数 $T$,表示测试数据组数,其中 $1\le T\le 10^5$。
每组测试数据包含两行:
- 第一行输入一个整数 $n$,表示树苗数量,其中 $1\le n\le 2\times 10^5$
- 第二行输入 $n$ 个整数 $a_1,a_2,\ldots,a_n$,其中 $0\le a_i\le 10^9$
保证单个测试文件中所有测试数据的 $n$ 之和不超过 $2\times 10^5$。
输出描述
对于每组测试数据,输出一行一个整数,表示最优放置防风核心时所需的最小能量 $P$。
样例
输入
2
5
2 1 4 2 3
3
10 0 10
输出
5
11
样例解释
第一组把核心放在 $p=4$ 时,各树苗对能量的要求 $a_i+\lvert i-p\rvert$ 依次为:
\[5,3,5,2,4.\]最大值为 $5$,且不存在能让最大值更小的放置位置,因此答案为 $5$。
第二组把核心放在 $p=2$ 时,各树苗对能量的要求依次为:
\[11,0,11.\]最大值为 $11$,所以答案为 $11$。
思路分析
固定核心位置 $p$ 后,所需的最小能量是:
\[f(p)=\max_{1\le i\le n}\left(a_i+\lvert i-p\rvert\right).\]直接枚举 $p$,再扫描所有树苗计算最大值,会产生 $O(n^2)$ 的时间复杂度。关键是拆开绝对值:
\[\lvert i-p\rvert=\max(i-p,p-i).\]因此:
\[a_i+\lvert i-p\rvert =\max\left((a_i+i)-p,(a_i-i)+p\right).\]再对所有 $i$ 取最大值。定义两个与 $p$ 无关的量:
\[R=\max_{1\le i\le n}(a_i+i), \qquad L=\max_{1\le i\le n}(a_i-i).\]则:
\[f(p)=\max(R-p,L+p).\]只需扫描数组一次求出 $R$、$L$,之后枚举 $p=1,2,\ldots,n$,每个位置用 $O(1)$ 时间计算 $f(p)$,取其中最小值即可。两次线性扫描的总复杂度仍为 $O(n)$。
其中 $R-p$ 随 $p$ 增大而减小,$L+p$ 随 $p$ 增大而增大。虽然还可以利用两式交点只检查附近的整数位置,但直接枚举全部 $n$ 个位置已经满足线性复杂度,也更不容易出现取整或边界错误。
正确性证明
设算法预处理得到:
\[R=\max_i(a_i+i),\qquad L=\max_i(a_i-i).\]对任意合法放置位置 $p$,有:
\[\begin{aligned} \max_i(a_i+\lvert i-p\rvert) &=\max_i\max\left((a_i+i)-p,(a_i-i)+p\right)\\ &=\max\left(\max_i(a_i+i)-p,\max_i(a_i-i)+p\right)\\ &=\max(R-p,L+p). \end{aligned}\]所以算法对每个 $p$ 计算出的 need,恰好等于核心放在该位置时保护全部树苗所需的最小能量,而不是近似值。
题目允许的位置恰好是所有整数 $p\in[1,n]$。算法逐一枚举了这个集合中的每个位置,并取对应 need 的最小值,因此不会遗漏任何可行位置。故最终输出值等于:
即题目要求的最小防风核心能量。
题解代码
import sys
def solve() -> None:
data = list(map(int, sys.stdin.buffer.read().split()))
if not data:
return
t = data[0]
pos = 1
answers = []
for _ in range(t):
n = data[pos]
pos += 1
right_max = -10**30 # max(a_i + i)
left_max = -10**30 # max(a_i - i)
for i in range(1, n + 1):
value = data[pos]
pos += 1
right_max = max(right_max, value + i)
left_max = max(left_max, value - i)
best = 10**30
for p in range(1, n + 1):
need = max(right_max - p, left_max + p)
best = min(best, need)
answers.append(str(best))
sys.stdout.write("\n".join(answers))
if __name__ == "__main__":
solve()
复杂度分析
设所有测试数据的树苗数量之和为 $N$。
时间复杂度:$O(N)$。每组数据扫描 $a_i$ 一次并枚举核心位置一次。
空间复杂度:算法本身的额外空间为 $O(1)$;若计入一次性读入的整数数组和输出缓冲区,则为 $O(N+T)$。
易错点
- 树苗坐标从 $1$ 开始,计算 $a_i+i$ 和 $a_i-i$ 时不要误用从 $0$ 开始的数组下标
- 核心只能放在整数坐标 $1$ 到 $n$,不能直接把连续函数交点当成合法位置
- 拆分后是 $R-p$ 与 $L+p$,两个符号不要写反
- 对所有 $i$ 取最大值后,结果是 $\max(R-p,L+p)$,不是两项之和
- 即使所有 $a_i=0$,当 $n>1$ 时答案通常也不为 $0$,因为核心能量还需覆盖距离损耗
- 在固定宽度整数语言中,建议使用 64 位整数保存中间结果和答案
知识点总结
| 题目 | 核心考点 | 关键结论 |
|---|---|---|
| 幻觉字符串 | 字符串、端点选择、线性扫描 | 合法性只约束首尾,最靠外的一对幻觉字符构成最长合法子串 |
| 防风核心 | 绝对值拆分、极值预处理 | 预处理 $R=\max(a_i+i)$、$L=\max(a_i-i)$,固定位置的需求为 $\max(R-p,L+p)$ |
这两题都不依赖复杂数据结构,重点是识别目标函数中真正影响答案的信息。第一题把子串问题压缩为两个端点,第二题把每个位置对全部树苗的评估压缩为两个全局最大值;完成这两步后,都可以在线性时间内解决。