2026-02-07:统计二进制回文数字的数目。用go语言,给定一个不小于 0 的整数 n。考察区间 0 到 n 内的每个整数,把它用二进制表示并去掉前导零;如果该二进制串正着读和反着读都一样,则认为该整数是二进制回文数。请统计并返回在 0 到 n 范围内满足这一条件的整数个数。特别说明:把 0 的二进制表示视为 "0",也算作回文。
0 <= n <= 1000000000000000。
输入: n = 9。
输出: 6。
解释:
在范围 [0, 9] 内,二进制表示为回文数的整数 k 有:
0 → "0"
1 → "1"
3 → "11"
5 → "101"
7 → "111"
9 → "1001"
[0, 9] 中的所有其他值的二进制形式都不是回文。因此,计数为 6。
题目来自力扣3677。
解题思路(分步描述)
1. 二进制回文数的规律
- 一个二进制回文数由它的前半部分唯一确定。
- 例如:若长度为奇数 m,则二进制形式为
left+中间位+reverse(left),其中left是前m/2位(向下取整),中间位就是二进制串中间那个数字。 - 若长度为偶数 m,则二进制形式为
left+reverse(left)。
因此我们可以按二进制位数 m 来分类统计回文数。
2. 确定 n 的二进制长度
设 m = bits.Len(uint(n))(n≥1),若 n=0,直接返回 1。
n 的二进制有效长度是 m 位(最高位是 1)。
3. 统计二进制长度小于 m 的所有回文数
对于每个可能的长度 L:
- 如果 L=1(二进制只有 1 位),回文数有:0、1(长度为 1 时最高位是 1)。
- 如果 L 是奇数:前半部分(不包括中间位)长度 k = (L-1)/2,前半部分最小值是 1(二进制为 1),最大值是 2^k-1,所以长度为 L 的奇数长度回文数有 2^k 个。
- 如果 L 是偶数:前半部分长度 L/2,不能全为 0(否则前导 0 会使长度变短),所以前半部分从 1 到 2^(L/2)-1,所以长度为 L 的偶数长度回文数有 2^(L/2-1) 个吗?不,前半部分的每一位都可取 0/1(但首位必须为 1),因此长度为 L 的偶数回文数有 2^(L/2-1) 个?这里需要推导清楚,但不展开。
实际上算法中使用一个参数 k 来简化累加:
- 对于 m-1 位的所有回文数,可以通过公式
2<<k - 1来求得(这是奇数偶数两种情况的总和)。 - 当 m 本身是偶数时,还要加上长度恰好为 m 的偶数回文数(不含奇数)的某种组合数量,公式里是
ans += 1 << k(对 k 的调整)。
4. 统计二进制长度等于 m,且回文数的左半部分小于 n 的左半部分的数量
- 将 n 的左半部分提取出来作为
left,即n >> (m/2)(如果 m 是偶数,就是高半部分;如果 m 是奇数,就是高 (m+1)/2 位)。 - 左半部分最小是
1<<k(k 是前半部分除去中间位后的长度,即(m-1)/2),最大是left-1。 - 所以有
left - 1<<k个回文数的左半部分小于 n 的左半部分且长度等于 m,这些肯定小于 n。
5. 统计二进制长度等于 m,且回文数的左半部分等于 n 的左半部分的情况
这种情况下,回文数由左半部分唯一确定。我们构造出对应的回文数:
- 用
left和 m 的奇偶性生成右半部分:右半部分 =reverse(left)去掉可能的中间位。 - 构造出来的回文值 =
(左半部分) << (m/2)| 右半部分。 - 检查它是否 ≤ n,如果 ≤ n 则加 1(因为等于左半部分的情况可能有多个,但这里只关心左半部分等于 n 的左半部分,也就是回文数可能与 n 相等或者小于 n)。
6. 例子 n=9 过程:
- m = 4(二进制 1001)
- 长度小于 4 的回文数:
长度 1: 0, 1
长度 2: 11(3)
长度 3: 101(5), 111(7)
共 5 个吗?这里算一下:0,1,3,5,7 共 5 个。
但 0 已经在长度 1 算入。所以长度小于 4 时,二进制回文数总数 = 5(对应 ans=5) - 二进制长度等于 4 且左半部分小于 9 的左半部分:
n=9 左半部分left = 9>>2 = 2(二进制 10),k= (4-1)/2=1,1<<1 = 2,left - 2 = 0,所以没有。 - 二进制长度等于 4 且左半部分等于 9 的左半部分:
left=2(二进制 10),m=4(偶),生成右半部分 = reverse(10) = 01(二进制)
构造出的回文 = 1001(即 9),等于 n,所以 +1。 - 总数 = 5 + 0 + 1 = 6。
复杂度分析
-
时间复杂度 O(log n) 或 O(1)
整个算法只使用了位运算和常数次算术运算,与 n 的大小无关,只与 n 的二进制位数 m 有关,最多 64 位。因此时间可视为常数时间 O(1)。 -
空间复杂度 O(1)
只用几个临时变量,不随输入规模增长。
总时间复杂度:O(1)
总额外空间复杂度:O(1)
Go完整代码如下:
package main
import (
"fmt"
"math/bits"
)
func countBinaryPalindromes(n int64) int {
if n == 0 {
return 1
}
m := bits.Len(uint(n))
k := (m - 1) / 2
// 二进制长度小于 m
ans := 2<<k - 1
if m%2 == 0 {
ans += 1 << k
}
// 二进制长度等于 m,且回文数的左半小于 n 的左半
left := n >> (m / 2)
ans += int(left) - 1<<k
// 二进制长度等于 m,且回文数的左半等于 n 的左半
right := bits.Reverse32(uint32(left>>(m%2))) >> (32 - m/2)
if left<<(m/2)|int64(right) <= n {
ans++
}
return ans
}
func main() {
n := int64(9)
result := countBinaryPalindromes(n)
fmt.Println(result)
}
Python完整代码如下:
# -*-coding:utf-8-*-
def count_binary_palindromes(n: int) -> int:
if n == 0:
return 1
# 计算二进制长度
m = n.bit_length()
k = (m - 1) // 2
# 二进制长度小于 m
ans = (2 << k) - 1
if m % 2 == 0:
ans += 1 << k
# 二进制长度等于 m,且回文数的左半小于 n 的左半
left = n >> (m // 2)
ans += left - (1 << k)
# 二进制长度等于 m,且回文数的左半等于 n 的左半
# 处理右半部分的反转
if m % 2 == 1:
# 奇数长度,去掉中间位
right_part = left >> 1
else:
right_part = left
# 反转右半部分
# 计算反转后的右半部分
reversed_right = 0
temp = right_part
for i in range(m // 2):
reversed_right = (reversed_right << 1) | (temp & 1)
temp >>= 1
# 构建完整的回文数
if m % 2 == 0:
palindrome = (left << (m // 2)) | reversed_right
else:
# 奇数长度,中间位是 left 的最低位
palindrome = ((left >> 1) << (m // 2 + 1)) | ((left & 1) << (m // 2)) | reversed_right
# 如果回文数小于等于 n,则加1
if palindrome <= n:
ans += 1
return ans
def main():
n = 9
result = count_binary_palindromes(n)
print(result)
if __name__ == "__main__":
main()
C++完整代码如下:
#include <iostream>
#include <cstdint>
#include <bit>
// 用于反转 32 位整数的辅助函数(C++20 之前没有 std::reverse 用于整数)
uint32_t reverseBits32(uint32_t x) {
// 使用分治法反转位
x = ((x & 0x55555555) << 1) | ((x >> 1) & 0x55555555);
x = ((x & 0x33333333) << 2) | ((x >> 2) & 0x33333333);
x = ((x & 0x0F0F0F0F) << 4) | ((x >> 4) & 0x0F0F0F0F);
x = ((x & 0x00FF00FF) << 8) | ((x >> 8) & 0x00FF00FF);
x = (x << 16) | (x >> 16);
return x;
}
int countBinaryPalindromes(int64_t n) {
if (n == 0) {
return 1;
}
// 计算二进制长度
int m = 64 - __builtin_clzll(static_cast<uint64_t>(n));
int k = (m - 1) / 2;
// 二进制长度小于 m
int ans = (2 << k) - 1;
if (m % 2 == 0) {
ans += 1 << k;
}
// 二进制长度等于 m,且回文数的左半小于 n 的左半
int64_t left = n >> (m / 2);
ans += static_cast<int>(left - (1LL << k));
// 二进制长度等于 m,且回文数的左半等于 n 的左半
uint32_t right = reverseBits32(static_cast<uint32_t>(left >> (m % 2))) >> (32 - m / 2);
if ((left << (m / 2)) | static_cast<int64_t>(right) <= n) {
ans++;
}
return ans;
}
int main() {
int64_t n = 9;
int result = countBinaryPalindromes(n);
std::cout << result << std::endl;
return 0;
}