动态规划VS 普通迭代
原题如下:
给你一个整数 n ,对于 0 <= i <= n 中的每个 i ,计算其二进制表示中 1 的个数 ,返回一个长度为 n + 1 的数组 ans 作为答案。
审题:
题目所表达的意思是给我们一个整数n,例如给我们的整数是5。那么我们要计算0、1、2、3、4、5这几个数所对应转换为二进制后其中1的数目,最后以数组的形式返回这些结果,如下:
解读:下标为3的数组元素:3 转换为二进制后为(11)有2个1,所以下标3所对应的数据是2。
思路:
思路一:普通迭代方式
对于本题,通俗的说就是我们申请一个数组大小为 n +1的数组,计算每个下标对应的二进制中1的个数,将这个总数记录到该下标位置中即可。既然如此,那么我们就遍历计算就可以。
但是这种方式没有利用到已经计算的记录所以效率比较低,不过还是实现一下.
思路二:动态规划
动态规划的核心思想是:利用已知记录完成对未知记录的计算。
所以更好的方式是使用动态规划,有关动态规划的思想及解题步骤在往期的一道OJ题文章中有详细分享,有兴趣的可以了解一下。
动态规划
对于动态规划我们没有必要去深究原理,要想理解动态规划,更多的还是去解题找感觉。
下面根据动态规划的思想与解题步骤我们来进行解读。
解题步骤:
1.定义dp数组所表示的含义
给我们一个整数 n ,题目要求我们返回一个数组。而数组元素所表示的是0<=i(下标)<=n所对应二进制中1的数目。
因此定义dp数组为:下标 i 所对应的二进制中 1 的个数为 dp[i]。
2.确定递推公式
在此之前补充点知识:
先来看十进制,我们在十进制的世界中将一个整数扩大十倍是不是在低位补一个零,或者说将该整数左移一位。那么同样在二进制的世界中,将一个数扩大两倍,也就是在低位补一个0,或者说左移动一位。它们是相通的
再来看一点,在十进制的世界中有2位数,3位数……同样在二进制的世界中 0~4 表示为:00、01、10…… 5~8 表示为:101、110……也是类似的,理解这两点是确定递推公式的关键。
最后一步:假设要我们求解n,现在我们已经知道了下标为n-1及其之前数的二进制数中 1 的数目。那么dp[n]的值取决于什么?
基于上面所分享的补充知识,dp[n]的值要分偶数与奇数来考虑,所以存在下面的递推公式。
如果n是偶数dp[n]=dp[n/2];
如果n是奇数dp[n]=dp[n-1]+1。
3.确定初始条件及边界情况
根据递推公式当n=1的时候是不能由递推公式分解得出,因为数组下标无负数,当 n =0也不能由递推公式得出所以要直观的给出dp[1]与dp[0]的结果。
边界情况自然是数组不能越界访问。
4.遍历顺序(计算顺序)
根据递推公式要求解等式左边的必须知道等式右边的(n>n-1)所以要从小到大进行遍历。
代码实现:
普通迭代:
//C实现
int oneSum(int number)
{
int sum=0;//标记1的总数目
int i=0;//标记余数
while(number>0)
{
//先模运算取余,再做除法
i=number%2; //先模运算取余
if(i==1)sum++;//如果余数为1,则将sum++
number/=2;//再做除法
}
return sum;//返回最后的总数
}
int* countBits(int n, int* returnSize){
int*result=(int*)malloc(sizeof(int)*(n+1));//将结果放到数组中
*returnSize=(n+1);
for(int i=0;i<=n;i++)//计算每个下标所对应二进制数中1的总数目
{
int sum=oneSum(i);//调用计算的接口函数
result[i]=sum;
}
return result;
}
动态规划:
//C++实现
class Solution {
public:
vector<int> countBits(int n) {
vector<int>dp(n+1);//申请一个dp数组
dp[0]=0;
if(n==0)return dp;//防止越界
dp[1]=1;//不能有递推公式求解的要直观的给出答案
for(int i=2;i<=n;i++)
{
if(i%2==0)//如果是偶数
{
dp[i]=dp[i/2];//低位补0,所以与i/2的位置相同
}
else//是奇数
{
dp[i]=dp[i-1]+1;//在前一位的基础上再低位补1,所以比前一个多1
}
}
return dp;
}
};
序:
到现在为止每次碰见动态规划类型的题目都还会让我思考很长时间。动态规划的思想和基本操作不难,但是总是比较棘手,所以也就只有不断加强练习。
这次的解题分享就到这里。
我是老胡,感谢阅读!!!❤️ ❤️