小欧的数组构造
小欧的数组构造
OPPO 笔试真题 · 2024 秋招 · 移动端
判题请到牛客原题提交。
题目描述
小欧有一个整数数组 ,她已经忘记了 数组的构成,但她还记得 数组, 数组中的每一项记录的是 数组两个相邻的数之和,也就是说 。
小欧很喜欢正数,她想知道在原来的 数组中最多可能有几个正数,请输出最多可能的正数个数
输入格式
第一行输入一个整数 表示数组长度。
第二行输入 个整数表示数组 。
输出格式
输出一个整数表示答案。
示例
示例 1
输入:
3
1 2输出:
2说明:
a数组可能是[1,0,2],[2,-1,3],[0,1,1],[-2,3,-1]等,但可以证明,正数数量最多只可能是2。
解题思路
第一步:理解问题本质
我们只知道 个数 ,不知道 本身。
方程有 个、未知数有 个,所以自由度是 1:
只要确定 ,其余的数就被唯一确定了。
我们要挑一个 ,让 里正数的个数尽可能多。
第二步:暴力解法
枚举 的取值,每种取值都递推一遍求出 ,数一数有几个正数。
问题在于 可以取任意整数(范围无界),枚举不现实;
即使只看「有意义的范围」, 时整个做法也至少是
的量级( 是枚举范围的大小)。这个方法只能用来验证小数据。
第三步:写出通项
把递推展开,观察 与 的关系:
的符号在交替变化,常数项则一级一级往下减。把它写成统一形式:
也就是说:奇数位是 ,偶数位是 。
第四步:把「正数」变成对 的单侧限制
要求 (正数,不含 0):
- 若 是奇数:,这是对 的下界;
- 若 是偶数:,这是对 的上界。
于是问题变成:选一个整数 ,让它满足尽量多的这种「单侧限制」。
每个限制只关心 在某个分界点的哪一侧。
第五步:扫描分界点
「满足的个数」只会在分界点处变化,所以不用枚举 ,只要看这些点:
- 奇数位的限制在 处开始生效,记为 ;
- 偶数位的限制在 处失效(因为条件只到 为止),记为 ;
- 当 时,所有偶数位都满足、所有奇数位都不满足,
所以起点是「偶数位的个数」。
把所有事件按 从小到大排序,维护一个计数器,途中出现的最大值就是答案。
完整代码实现
import sys
def solve():
"""把每个 a_i > 0 化成对 a_1 = x 的区间限制,扫描分界点求最大值"""
data = sys.stdin.buffer.read().split()
if not data:
return
n = int(data[0])
b = [int(t) for t in data[1:1 + n - 1]]
# c_1 = 0,c_i = b_{i-1} - c_{i-1}
delta = {} # x 走到这个位置时,「满足条件的个数」的变化量
cur = 0 # 当前有 cur 个限制成立
c = 0
for i in range(1, n + 1):
if i == 1:
c = 0
else:
c = b[i - 2] - c
if i % 2 == 1:
# a_i = c + x > 0 => x >= -c + 1,即 x 到达该点时这个限制才成立
pos = -c + 1
delta[pos] = delta.get(pos, 0) + 1
else:
# a_i = c - x > 0 => x <= c - 1,即 x 越过 c-1 之后这个限制失效
cur += 1 # x 取 -inf 时偶数位全部为正
pos = c # 走到 x = c 时 -1
delta[pos] = delta.get(pos, 0) - 1
best = cur
for pos in sorted(delta):
cur += delta[pos]
if cur > best:
best = cur
sys.stdout.write(str(best) + "\n")
if __name__ == "__main__":
solve()示例推演
样例:,。
第一步:算出常数项
于是
第二步:化成对 的限制
| 位置 | 表达式 | 要求 | 对 的限制 | 事件 |
|---|---|---|---|---|
| 1(奇) | 在 处 | |||
| 2(偶) | 在 处 | |||
| 3(奇) | 在 处 |
第三步:扫描
- 起点 :只有 2 号位满足,计数 ,最大值 ;
- 走到 :3 号位开始满足,计数 ,最大值 ;
- 走到 :1 号位开始满足(),同时 2 号位失效(),计数仍是 2。
所以答案是 2。
第四步:验证
取 ,得到 :、,
确实是一个合法解,其中正数有 2 个(0 不算正数)。
这和题面说明里的 [0,1,1] 一致。
再取 ,得到 ,同样有 2 个正数 —— 最优值 2 有多组解。
复杂度分析
| 解法 | 时间复杂度 | 空间复杂度 | 说明 |
|---|---|---|---|
| 枚举 并逐个递推 | 是枚举范围,而 本身无界,无法使用 | ||
| 通项 + 事件扫描 | 推荐,代价只有一次排序 |
易错点总结
1. 通项的符号写反
奇数位是 、偶数位是 。如果写反,所有上下界都会错位一项,
样例可能仍然「碰巧」正确,但换成别的数据就崩了。
2. 偶数位的分界点是 ,不是
条件是不等式 ,即 ,所以它在 时失效。
事件位置写错 1,答案就会差 1。
3. 0 不是正数
题目问的是正数,严格大于 0,所以奇数位的下界要 ()。
写成 会把 也算进去。
4. 扫描的起点是「偶数位的个数」
时偶数位 必然为正,奇数位 必然为负或零。
起点写 0 会漏掉「 取很小的负数」这一整类方案。
5. 数值范围
可以到 、 到 , 交替累加后可能达到 量级。
Python 整数不会溢出;用 C++ 写时 、 都要用 long long。
6. 不要假设
是原数组的第一个元素,可以是负数。题面说明里的 [-2,3,-1]
就来自 ,只枚举非负的 会漏掉不少情况。
扩展思考
- 若改成统计「最多的非负数」,只需把 换成 ,
所有分界点整体左移 1,骨架完全不变。 - 若约束变成 ,通项里 的系数会变成
,不再交替取 ,比较大小就变成有理数(或浮点 + 精度)问题。 - 若再要求 单调递增,那么「对 的单侧限制」会变成「 的区间」,
求交集即可,反而更简单。 - 这类题的通用套路:一个自由变量 + 每个位置给出一个关于它的不等式,
答案就是被最多区间覆盖的那个点,用「差分 + 扫描线」处理。