本篇博文将总结数学相关的内容,涉及概率组合的一些算法,比较简单。
求1的个数
问题描述
给定一个
问题分析
方法一
显然:可以通过不断的将整数
int OneNumber(int n)
{
int c = 0;
while (n!=0)
{
c += (n & 1);//n末尾最后一位不断和1作与
n >>= 1;//n=n>>1,n右移一位
}
return c;
}
方法二
每次将
int OneNumber2(int n)
{
int c = 0;
while (n!=0)
{
n = n&(n - 1);//将n的最后一个“1”清零
c++;//每清除一个1,c就加一
}
return c;
}
方法三:分治
假定能够求出
为了节省空间,用一个
- 高
16 位记录a ,低16 位记录b ; -
(0xFFFF0000&N) 筛选得到a ; -
(0x0000FFFF&N) 筛选得到b ; -
(0xFFFF0000&N)+(0x0000FFFF&N)>>16
如何得到高
- 分治往往伴随着递归
递归过程:
如果二进制数
N 是16 位,则统计N 的高8 位 和低8 位各自1 的数目a 和b ,而a、b 用某一个16 位数X 存储,则使用0xFF00、0x00FF 分别于X 做与操作,筛选出a 和b ;原问题中的数据是32 位,因此分别需要2 个0xFF00/0x00FF ,即0xFF00FF00/0x00FF00FF 。如果二进制数是
8 位,则统计高4 位和低4 位各自1 的数目,使用0xF0/0x0F 分别做与操作,筛选出高4 位和低4 位;原问题中的数据是32 位,则分别需要4 个0xF0/0x0F ,即0xF0F0F0F0/0x0F0F0F0F 。如果是
4 位则统计高2 位和低2 位各自1 的数目,用0xC/0x3 筛选(高2 位1100 十六进制表示0xC ,低2 位0011 十六进制表示为0x3 );原问题中的数据是32 位,故各需要需要8 个0xC/0x3 ,即0xCCCCCCCC/0x33333333 。如果是
2 位则统计高1 位和低1 位各自1 的数目,用0x2/0x1 筛选;原问题中的数据是32 位,(因为在十六进制中,以四位为一个单位,则高1 位为1010 即为0xA ,需要8 个0xA ,同理低1 位0101 即为0x5 ,也需要8 个 )故各需要8 个0xA/0x3 ,即为0xAAAAAAAA/0x33333333 。
int HammingWeight(unsigned int n)
{
//(n & 0x55555555)每相邻两位忽略高位保留低位为1的二进制序列,
//(n & 0xaaaaaaaa)>>1每相邻两位忽略低位保留高位为1的二进制序列并右移1位,高位补零。
//上面两个子序列相加,则为每相邻两位高位和对应低位为1的相加,往前一位进1。
//也就是检查每对相邻的2位有几个1
n = (n & 0x55555555) + ((n & 0xaaaaaaaa) >> 1);
//每相邻的四位有几个1
n = (n & 0x33333333) + ((n & 0xcccccccc) >> 2);
//每相邻的八位有几个1
n = (n & 0x0f0f0f0f) + ((n & 0xf0f0f0f0) >> 4);
//每相邻的十六位有几个1
n = (n & 0x00ff00ff) + ((n & 0xff00ff00) >> 8);
//32位有几个1
n = (n & 0x0000ffff) + ((n & 0xffff0000) >> 16);
return n;
}
int main()
{
int c = HammingWeight(16);
cout << c << endl;
}
在采用
总结与应用
-
HammingWeight 使用了分治/递归的思想,将问题巧妙解决,降低了运算次数。 - 如果定义两个长度相等的
0/1 串中对应位不相同的个数为海明距离(即码距),则某0/1 串和全0 串的海明距离即为这个0/1 串中1 的个数。 - 两个
0/1 串的海明距离,即两个串异或值的1 的数目,因此,该问题在信息编码等诸多领域有广泛应用。
跳跃问题
问题描述
给定非负整数数组,初始时在数组起始位置放置一机器人,数组的每个元素表示在当前位置机器人最大能够跳跃的数目。它的目的是用最少的步数到达数组末端。例如:给定数组
如:
问题分析
由上图我们可以看出当前跳的范围为蓝色的
初始步数
step 赋值为0 ;记当前步的控制范围是
[i,j] ,则用k 遍历i 到j ;
计算A[k]+k 的最大值,记做j2 ;A[k] 表示当前位置最远能跳的距离。step++ ;继续遍历[j+1,j2] ;
每一个
实现代码
int Jump(int* a,int size)
{
if (size == 1)
return 0;
int i = 0;
int j = 0;//初始可跳的范围即为[i,j]
int k,j2;
int step = 0;
while (j<size)
{
step++;
j2 = j;
for (k = i; k <= j; k++)//遍历当前step可跳的范围来确定下一跳的范围
{
j2 = max(j2, k + a[k]);
if (j2 > size - 1)
return step;
}
i = j + 1;//上一跳的终点的下一个格子为下一跳的起点,注意a[k]为最大可跳的距离,最少可跳一步。
j = j2;//下一跳终点
if (j < i)
return -1;
}
return step;
}
Jump问题总结
虽然从代码上看有两层循环,但是我们分析执行过程可知只是从序列头跳到序列尾,时间复杂度只有
该算法在每次跳跃中,都是尽量跳的更远,并记录
错位排列问题
问题描述
问题分析
- 假定
n 个数的错位排列数目为dp[n] - 先考察数字
n 的放置方法:显然,n 可以放在从1 到n−1 的某个位置,共n−1 种方法;假定放在了第k 位。 - 对于数字
k :
要么放置在第n 位
要么不放置在第n 位。
数字k放置在第n位
相当于数字
数字k不放置在第n位
将数字
错位排列递推公式
综上,
初值
只有
只有
则递推公式为:
实现代码
int dislocationSorting(int n)
{
int* dp = new int[n];
dp--;
dp[1] = 0;
dp[2] = 1;
for (int i = 3; i <= n; i++)
dp[i] = (i - 1)*(dp[i - 1] + dp[i - 2]);
return dp[n];
}
int main()
{
int c = dislocationSorting(2);
cout << c << endl;
}