小O的数位翻转
小O的数位翻转
OPPO 笔试真题 · 2024 秋招 · 研发通用岗 / 2024 秋招 · 后端开发
判题请到牛客原题提交。
题目描述
小O有一个长度为 的数组 ,他现在想要选择其中一个数字进行“二进制翻转”操作,他想知道有多少种可能的选择方式,使得操作后的数组总和比不操作更大。
二进制翻转:指将 的二进制翻转,翻转后去除前导 0。
(例如:。)
输入格式
第一行输入一个正整数 ,表示数组 的长度。
第二行输入 个正整数 ,表示数组 的元素。
输出格式
在一行上输出一个整数,表示合法的方案数。
示例
示例 1
输入:
5
11 12 11 13 12输出:
2说明:
,翻转后变为 ,大于翻转前;
和 翻转后均小于翻转前。
示例 2
输入:
6
1 2 3 4 5 6输出:
0解题思路
第一步:理解问题本质
只操作一个数,所以「操作后的总和」很好写。设原来的总和是 ,被选中
翻转的是 ,那么操作后
于是
换句话说,每个位置能不能成为答案,只取决于它自己的值,和其它元素
完全无关。这样一来,题目就退化成了「数一数有多少个 满足
」。
第二步:怎么求一个数的翻转值
「二进制翻转」要分两步做,顺序不能反:
- 写出二进制:Python 里就是
bin(x)[2:],例如 12 得到'1100'; - 把这一串整体反序,再读回整数:
'0011'前面的 0 会被int(..., 2)
自动忽略,正好对应题面说的「翻转后去除前导 0」。
所以
第三步:逐个判断,按下标计数
从头到尾扫一遍数组,满足 就让计数器加一。
两个容易忽略的细节:
- 必须是严格大于。二进制本身是回文的数(、、
、)翻转后还是自己,总和不变,不能算。 - 相同数值要重复计数。示例 1 里
11出现了两次,两次都是独立的选择
方式,答案才是 2。按下标统计,不要按数值去重。
完整代码实现
import sys
def bit_reverse(x: int) -> int:
"""把 x 的二进制表示反序(自动去掉反序后产生的前导 0)"""
bits = bin(x)[2:] # 例如 12 -> '1100'
return int(bits[::-1], 2) # '0011' -> 3,int(..., 2) 会忽略前导 0
def solve() -> None:
"""逐个判断 f(a_i) > a_i 是否成立,统计个数"""
data = sys.stdin.buffer.read().split()
if not data:
return
n = int(data[0])
cnt = 0
for i in range(1, n + 1):
x = int(data[i])
if bit_reverse(x) > x:
cnt += 1
sys.stdout.write(str(cnt) + "\n")
if __name__ == "__main__":
solve()示例推演
示例 1:,数组为 11 12 11 13 12。
| 下标 | 二进制 | 翻转后 | 是否变大 | ||
|---|---|---|---|---|---|
| 1 | 11 | 1011 | 1101 | 13 | 是 |
| 2 | 12 | 1100 | 0011 | 3 | 否 |
| 3 | 11 | 1011 | 1101 | 13 | 是 |
| 4 | 13 | 1101 | 1011 | 11 | 否 |
| 5 | 12 | 1100 | 0011 | 3 | 否 |
共有 2 个位置满足条件,输出 2。
示例 2:1 2 3 4 5 6。
| 二进制 | 翻转后 | 是否变大 | ||
|---|---|---|---|---|
| 1 | 1 | 1 | 1 | 否(相等) |
| 2 | 10 | 01 | 1 | 否 |
| 3 | 11 | 11 | 3 | 否(相等) |
| 4 | 100 | 001 | 1 | 否 |
| 5 | 101 | 101 | 5 | 否(相等) |
| 6 | 110 | 011 | 3 | 否 |
一个都不满足,输出 0。
复杂度分析
| 解法 | 时间复杂度 | 空间复杂度 | 说明 |
|---|---|---|---|
| 字符串反序(本文) | 每个数转一次二进制串,(不超过 30 位) | ||
| 逐位取模重拼 | 从低位往高位拼 res = res * 2 + digit,效果等价,常数更小 |
、每个数不超过 30 位,总运算量在 次以内,
字符串写法完全够用。
易错点总结
1. 把「翻转」的位数搞错
翻转的是实际的二进制位数,不是固定 30 位或 32 位。12 是 1100 而
不是 00000000000000000000000000001100,反序之后要丢掉前导 0,得到的
是 3 而不是一个巨大的数。
2. 手写按位累加时的方向
如果不用字符串,就要从最低位开始取 x % 2 往结果里拼:res = res * 2 + x % 2。以 12 为例,依次取到 0、0、1、1,结果是((0*2+0)*2+1)*2+1 = 3,与反序一致。从高位开始拼会得到另一个数。
3. 相等的情况不能计数
题目要求总和变大。二进制回文的数翻转后不变,例如
、。判断要写 >,不能写 >=。
4. 按下标统计,不要按值去重
示例 1 中 11 出现两次,如果先去重再判断,答案会变成 1。
5. 用字符串直接比大小
'1101' > '1011' 这种比较只在两个串等长时才和数值比较一致。12 的反序
串 '0011' 和 '1100' 首字符不同就能骗过字符串比较。一律先 int(..., 2)
再比。
扩展思考
- 一个数翻转后比自己大,等价于「最低位是 1,且最高位到最低位不是回文」。
可以据此写出 的判定,不必真的构造字符串。 - 如果允许操作任意多个不同的位置,问最多能给总和增加多少:每个位置
的收益是 ,把正收益全部累加即可(互不影响);若每个位置
最多选一次且允许负数,就变成 0/1 背包。 - 如果把操作换成「按位取反(长度不变)」,,
是位数,此时悬空的问题变成「哪些数取反后会变大」,答案与最高位是否为 0
有关。