《算法分析与设计》Week 7

本文探讨了如何高效计算从0到给定整数范围内所有数的二进制表示中1的数量。通过发现数之间的内在联系,提出了一种线性时间复杂度的解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

338. Counting Bits


Description:

Given a non negative integer number num. For every numbers i in the range 0 ≤ i ≤ num calculate the number of 1's in their binary representation and return them as an array.

Example:
For num = 5 you should return [0,1,1,2,1,2].

Follow up:

  • It is very easy to come up with a solution with run time O(n*sizeof(integer)). But can you do it in linear time O(n) /possibly in a single pass?
  • Space complexity should be O(n).
  • Can you do it like a boss? Do it without using any builtin function like __builtin_popcount in c++ or in any other language.


Solution:

一、题意理解

     给一个非负整数num,返回一个数组,数组中第i项(0 ≤ i ≤ num)是数字i的二进制表示串的1的数量。

     例如,给定数字num = 5,从0~5的二进制表示及1的数量为:

         0  0000  0

         1  0001  1

         2  0010  1

         3  0011  2

         4  0100  1

         5  0101  2

     所以,返回结果数组为[0, 1, 1, 2, 1, 2]。


二、分析

     1、朴素法就是从0到num进行循环,依次对每个数进行移位操作,计算二进制串1的数量,时间复杂度为O(n*sizeof(integer))

     2、但我们可以寻找一种前后对应关系,即某两个数在二进制表示上是否有相关的关系。类似斐波那契数列,找到一种状态转移方程。即设F(a)为整数a的二进制串1的数量,G( F(a) )是某一种运算,使得F(b) = G(F(a)),其中b是和a有某种关系的另一个整数。

     3、幸运地是,我们可以找到这样的两个数,有F(b) = F(a) + 1,其中,a <b 且满足a = b&(b-1)。那为什么当a和b满足 a = b&(b-1)时,就有F(b) = F(a) + 1呢?很简单,b&(b-1)的意义是清除b的最后一个set位(set位是指二进制串中1的位置)。举个例子如下

                     b       12   1100      2

                  b-1       11   1011      3

      a = b&(b-1)       8    1000      1

      这样,既然b清除了最后一个set位得到a,那么b的1的数量自然就比a的多一个。

     4、得出状态转移方程,我们就很容易能写出代码如下:

class Solution {
public:
    vector<int> countBits(int num) {
        vector<int> ret(num+1, 0);
        for (int i = 1; i <= num; ++i)
            ret[i] = ret[i&(i-1)] + 1;
        return ret;
    }
};

     5、时间复杂度为O(n),空间复杂度O(n)。
### 关于山东大学程序设计 Week5 的课程资料作业 尽管当前提供的引用并未直接提及山东大学程序设计思维实践的第5周具体内容,但从已有参考资料来看,可以推测该课程可能涉及的内容范围以及学习目标。 通常情况下,在类似的程序设计课程中,第五周的学习重点可能围绕以下几个方面展开: #### 可能的主题方向 1. **动态规划基础** 动态规划是一种解决多阶段决策过程最优化问题的方法。它通过将复杂问题分解成更简单的子问题来求解最优解[^2]。如果 Week5 涉及此主题,学生可能会接触到经典的动态规划问题,例如背包问题、最长公共子序列(LCS)、矩阵链乘法等。 2. **贪心算法的应用** 贪心算法的核心在于每一步都做出局部最优的选择,期望最终达到全局最优解。这种策略适用于某些特定场景下的问题,比如活动选择问题、霍夫曼编码等问题[^1]。 3. **图论初步** 图论作为离散数学的重要分支之一,在计算机科学中有广泛的应用价值。Week5 也可能引入基本概念如连通性检测、最小生成树(Kruskal 和 Prim 算法),或者单源最短路径(Dijkstra 算法)[^4]。 4. **高级数据结构介绍** 高级的数据结构能够显著提升解决问题效率。例如堆(Heap)用于实现优先队列;并查集(Union-Find Set)用来处理集合合并操作等等[^3]。 以下是基于上述假设整理的一道典型练习题及其解答思路: --- #### 练习题示例:最大连续子数组和 给定一个整数数组 `nums` ,找到其中具有最大和的一个连续子数组,并返回其总和。 ##### 思路分析: 采用 Kadane's Algorithm 来高效完成任务。核心思想是在遍历过程中维护两个变量——当前的最大子数组结束位置处的累加值(`current_sum`)以及迄今为止发现的整体最大值(`max_so_far`)。每当遇到新的元素时更新这两个量即可得出答案。 ```python def maxSubArray(nums): current_sum = nums[0] max_so_far = nums[0] for i in range(1, len(nums)): # 判断是否应该重新开始一个新的子数组还是延续之前的 current_sum = max(nums[i], current_sum + nums[i]) # 更新整体最大值 max_so_far = max(max_so_far, current_sum) return max_so_far ``` --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值