2026-02-07:统计二进制回文数字的数目。用go语言,给定一个不小于 0 的整数 n。考察区间 0 到 n 内的每个整数,把它用二进制表示并去掉前导零;如

15 阅读7分钟

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;
}

在这里插入图片描述