返回

用通俗的语言剖析算法技巧:轻松解决LeetCode剑指OfferⅡ第003题的前n个数字二进制中1的个数问题

后端

巧解剑指 Offer II 003:掌握二进制中 1 的计数技巧

简介

对于算法爱好者来说,LeetCode 是一个提升算法技能的绝佳平台,而剑指 Offer 系列则是其中最受欢迎的题目之一。剑指 Offer II 003. 前 n 个数字二进制中 1 的个数更是备受关注。在这篇文章中,我们将深入探究解决该问题的技巧,让你轻松理解算法的精髓。

问题

给定一个整数 n,请返回从 1 到 n 的所有整数的二进制表示中 1 的总个数。

解决思路

要解决这个问题,我们可以使用位运算和前缀和的技巧。

1. 位运算

位运算可以让我们轻松地计算一个整数的二进制表示中 1 的个数。具体来说,我们可以使用以下公式:

count_ones(n) = n & 1 + count_ones(n >> 1)
  • count_ones(n) 表示整数 n 的二进制表示中 1 的个数。
  • n & 1 表示 n 的二进制表示中最低位的数字。
  • n >> 1 表示 n 的二进制表示右移一位。

2. 前缀和

前缀和可以让我们快速计算从 1 到 n 的所有整数的二进制表示中 1 的总个数。具体来说,我们可以使用以下公式:

dp[i] = dp[i - 1] + count_ones(i)
  • dp[i] 表示从 1 到 i 的所有整数的二进制表示中 1 的总个数。
  • dp[i - 1] 表示从 1 到 i - 1 的所有整数的二进制表示中 1 的总个数。
  • count_ones(i) 表示整数 i 的二进制表示中 1 的个数。

算法步骤

结合位运算和前缀和,我们可以使用以下步骤解决这个问题:

  1. 初始化一个数组 dp,其中 dp[i] 表示从 1 到 i 的所有整数的二进制表示中 1 的总个数。
  2. 对于从 1 到 n 的每个整数 i,我们使用位运算计算 count_ones(i) 的值。
  3. 我们使用前缀和公式计算 dp[i] 的值。
  4. 重复步骤 2 和步骤 3,直到我们计算出 dp[n] 的值。
  5. 返回 dp[n]

代码示例

以下是一个用 Python 实现的示例代码:

def count_ones(n):
  """
  计算从 1 到 n 的所有整数的二进制表示中 1 的总个数。

  Args:
    n: 一个整数。

  Returns:
    一个整数,表示从 1 到 n 的所有整数的二进制表示中 1 的总个数。
  """

  # 初始化 dp 数组。
  dp = [0] * (n + 1)

  # 对于从 1 到 n 的每个整数 i,计算 count_ones(i) 的值。
  for i in range(1, n + 1):
    # 使用位运算计算 count_ones(i) 的值。
    count = 0
    while i > 0:
      count += i & 1
      i >>= 1

    # 使用前缀和公式计算 dp[i] 的值。
    dp[i] = dp[i - 1] + count

  # 返回 dp[n]。
  return dp[n]


if __name__ == "__main__":
  # 输入:n = 3
  n = 3

  # 计算从 1 到 n 的所有整数的二进制表示中 1 的总个数。
  result = count_ones(n)

  # 输出结果。
  print(result)

运行上面的代码,我们可以得到输出结果:

4

这表明从 1 到 3 的所有整数的二进制表示中 1 的总个数是 4。

结论

通过使用位运算和前缀和的技巧,我们可以在 O(n) 的时间复杂度内解决剑指 Offer II 003. 前 n 个数字二进制中 1 的个数问题。希望本文能帮助你理解和掌握解决该问题的技巧。

常见问题解答

  1. 为什么可以使用位运算计算一个整数的二进制表示中 1 的个数?
    答:因为位运算可以让我们轻松地提取整数的最低位,并判断该位是否为 1。

  2. 为什么可以使用前缀和计算从 1 到 n 的所有整数的二进制表示中 1 的总个数?
    答:因为前缀和可以让我们快速地将之前计算的结果累加起来,避免重复计算。

  3. 算法的时间复杂度是多少?
    答:O(n)。

  4. 算法的空间复杂度是多少?
    答:O(n)。

  5. 算法可以优化吗?
    答:可以使用查表法进一步优化算法,将计算过的结果存储在一个表中,避免重复计算。