返回

二进制中 1 的计数:算法中的动态思维

前端

前言

当我们踏入算法的殿堂时,动态规划的思想犹如一盏明灯,照亮着我们解决复杂问题的道路。它教会我们以一种系统化的方式将问题分解成更小的子问题,并通过复用计算结果来优化性能。

算法简单题:前 n 个数字二进制中 1 的个数

作为算法初学者的试金石,我们不妨从一个看似简单的题目入手:计算前 n 个数字二进制中 1 的个数。乍一看,这是一个直接的计数问题,但要找到一种高效的算法,动态规划的思维就显得尤为重要。

子问题:从 1 到 n 的二进制中 1 的个数

为了解决这个问题,我们首先将问题分解成更小的子问题:计算从 1 到 n 的每个数字二进制中 1 的个数。对于每个子问题,我们都可以采用以下步骤:

  1. 对于偶数 n :如果 n 是偶数,那么 n 的二进制形式与 n/2 的二进制形式相同,只是少了一位最低位的 0。因此,从 1 到 n 的二进制中 1 的个数等于从 1 到 n/2 的二进制中 1 的个数。
  2. 对于奇数 n :如果 n 是奇数,那么 n 的二进制形式与 n-1 的二进制形式相同,只是多了一位最低位的 1。因此,从 1 到 n 的二进制中 1 的个数等于从 1 到 n-1 的二进制中 1 的个数加上 1。

复用计算结果:动态规划的精髓

通过以上步骤,我们可以发现子问题之间存在重叠关系。例如,计算从 1 到 6 的二进制中 1 的个数时,需要用到从 1 到 3 的计算结果。动态规划的精髓就在于复用这些计算结果,避免重复计算。

递归实现:简洁高效的解法

基于动态规划的思想,我们可以使用递归来实现该算法:

int count_ones(int n) {
  if (n == 0) {
    return 0;
  }
  if (n % 2 == 0) {
    return count_ones(n / 2);
  } else {
    return count_ones(n - 1) + 1;
  }
}

在该递归函数中,我们利用了子问题之间的关系,通过递归调用来复用计算结果。对于每个子问题,我们只需要计算相邻子问题的答案,然后根据偶数或奇数情况进行调整即可。

复杂度分析:从指数到线性

通过动态规划优化后的算法复杂度为 O(n),其中 n 是输入数字。与暴力求解的指数复杂度 O(2^n) 相比,动态规划显著地提高了算法效率。

深入思考:动态规划的应用范畴

动态规划并非仅限于计算二进制中 1 的个数。事实上,它是一种解决各种优化问题的强大工具,例如:

  • 最长公共子序列
  • 背包问题
  • 最短路径问题
  • 编辑距离

理解动态规划的思想对于算法工程师至关重要。通过将复杂问题分解成更小的子问题,复用计算结果并采用递归或动态规划算法,我们可以找到高效且优雅的解决方案。