算法基础-->概率组合

本篇博文将总结数学相关的内容,涉及概率组合的一些算法,比较简单。

求1的个数

问题描述

给定一个 32 32 32 位无符号整数 N N N,求整数 N N N 的二进制数中 1 1 1 的个数。

问题分析

方法一

显然:可以通过不断的将整数 N N N 右移,判断当前数字的最低位是否为 1 1 1,直到整数 N N N 0 0 0 为止。平均情况下,大约需要 16 16 16 次移位和 16 16 16 次加法。

int OneNumber(int n)
{
	int c = 0;
	while (n!=0)
	{
		c += (n & 1);//n末尾最后一位不断和1作与
		n >>= 1;//n=n>>1,n右移一位
	}
	return c;
}
方法二

每次将 n n n 最后一个 1 1 1 0 0 0,能清多少次就说明有多少个 1 1 1,只需要 n & = ( n − 1 ) n\verb'&'=(n-1) n&=(n1) 即可。例如: n = 1010111101 n=1010111101 n=1010111101 n − 1 = 1010111100 n-1=1010111100 n1=1010111100,两数相与,则必可清除 n n n 最后一位 1 1 1

int OneNumber2(int n)
{
	int c = 0;
	while (n!=0)
	{
		n = n&(n - 1);//将n的最后一个“1”清零
		c++;//每清除一个1,c就加一
	}
	return c;
}
方法三:分治

假定能够求出 N N N 的高 16 16 16 位中 1 1 1 的个数 a a a 和低 16 16 16 位中 1 1 1 的个数 b b b,则 a + b a+b a+b 即为所求。

为了节省空间,用一个 32 32 32 位整数保存 a a a b b b

  • 16 16 16 位记录 a a a,低 16 16 16 位记录 b b b
  • ( 0 x F F F F 0000 & N ) (0xFFFF0000\verb'&'N) (0xFFFF0000&N) 筛选得到 a a a
  • ( 0 x 0000 F F F F & N ) (0x0000FFFF\verb'&'N) (0x0000FFFF&N) 筛选得到 b b b
  • ( 0 x F F F F 0000 & N ) + ( 0 x 0000 F F F F & N ) > > 16 (0xFFFF0000\verb'&' N) + (0x0000FFFF\verb'&'N)>>16 (0xFFFF0000&N)+(0x0000FFFF&N)>>16

如何得到高 16 16 16 位和低 16 16 16 位中 1 1 1 的个数 a a a b b b 呢?

  • 分治往往伴随着递归

递归过程:

  1. 如果二进制数 N N N 16 16 16 位,则统计 N N N 的***高 8 8 8 位*** 和***低 8 8 8 位***各自 1 1 1 的数目 a a a b b b,而 a 、 b a、b ab 用某一个 16 16 16 位数 X X X 存储,则使用 0 x F F 00 、 0 x 00 F F 0xFF00、0x00FF 0xFF000x00FF 分别于 X X X 做***与操作***,筛选出 a a a b b b;原问题中的数据是 32 32 32 位,因此分别需要 2 2 2 0 x F F 00 / 0 x 00 F F 0xFF00/0x00FF 0xFF00/0x00FF,即 0 x F F 00 F F 00 / 0 x 00 F F 00 F F 0xFF00FF00/0x00FF00FF 0xFF00FF00/0x00FF00FF

  2. 如果二进制数是 8 8 8 位,则统计高 4 4 4 位和低 4 4 4 位各自 1 1 1 的数目,使用 0 x F 0 / 0 x 0 F 0xF0/0x0F 0xF0/0x0F 分别做与操作,筛选出高 4 4 4 位和低 4 4 4 位;原问题中的数据是 32 32 32 位,则分别需要 4 4 4 0 x F 0 / 0 x 0 F 0xF0/0x0F 0xF0/0x0F,即 0 x F 0 F 0 F 0 F 0 / 0 x 0 F 0 F 0 F 0 F 0xF0F0F0F0/0x0F0F0F0F 0xF0F0F0F0/0x0F0F0F0F

  3. 如果是 4 4 4 位则统计高 2 2 2 位和低 2 2 2 位各自 1 1 1 的数目,用 0 x C / 0 x 3 0xC/0x3 0xC/0x3 筛选(高 2 2 2 1100 1100 1100 十六进制表示 0 x C 0xC 0xC,低 2 2 2 0011 0011 0011 十六进制表示为 0 x 3 0x3 0x3);原问题中的数据是 32 32 32 位,故各需要需要 8 8 8 0 x C / 0 x 3 0xC/0x3 0xC/0x3 ,即 0 x C C C C C C C C / 0 x 33333333 0xCCCCCCCC/0x33333333 0xCCCCCCCC/0x33333333

  4. 如果是 2 2 2 位则统计高 1 1 1 位和低 1 1 1 位各自 1 1 1 的数目,用 0 x 2 / 0 x 1 0x2/0x1 0x2/0x1 筛选;原问题中的数据是 32 32 32 位,(因为在十六进制中,以四位为一个单位,则高 1 1 1 位为 1010 1010 1010 即为 0 x A 0xA 0xA,需要 8 8 8 0 x A 0xA 0xA,同理低 1 1 1 0101 0101 0101 即为 0 x 5 0x5 0x5,也需要 8 8 8 个 )故各需要 8 8 8 0 x A / 0 x 3 0xA/0x3 0xA/0x3,即为 0 x A A A A A A A A / 0 x 33333333 0xAAAAAAAA/0x33333333 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;
}

在采用 H a m m i n g W e i g h t HammingWeight HammingWeight 方法时,对于任何一个 32 32 32 位无符号整数 N N N 只需要计算 5 5 5 次运算即可。

总结与应用
  • H a m m i n g W e i g h t HammingWeight HammingWeight 使用了分治/递归的思想,将问题巧妙解决,降低了运算次数。
  • 如果定义两个长度相等的 0 / 1 0/1 0/1 串中对应位不相同的个数为海明距离(即码距),则某 0 / 1 0/1 0/1 串和全 0 0 0 串的海明距离即为这个 0 / 1 0/1 0/1 串中 1 1 1 的个数。
  • 两个 0 / 1 0/1 0/1 串的海明距离,即两个串异或值的 1 1 1 的数目,因此,该问题在信息编码等诸多领域有广泛应用。

跳跃问题

问题描述

给定非负整数数组,初始时在数组起始位置放置一机器人,数组的每个元素表示在当前位置机器人***最大能够跳跃的数目***。它的目的是用最少的步数到达数组末端。例如:给定数组 A = [ 2 , 3 , 1 , 1 , 2 ] A=[2,3,1,1,2] A=[2,3,1,1,2],最少跳步数目是 2 2 2,对应的跳法是: 2 − > 3 − > 2 2->3->2 2>3>2

如: 2 , 3 , 1 , 1 , 2 , 4 , 1 , 1 , 6 , 1 , 7 2,3,1,1,2,4,1,1,6,1,7 2,3,1,1,2,4,1,1,6,1,7最少需要几步

问题分析

这里写图片描述

由上图我们可以看出当前跳的范围为蓝色的 1 1 1 范围,下一跳的范围为蓝色的 2 2 2 范围。

  1. 初始步数 s t e p step step 赋值为 0 0 0

  2. 记当前步的控制范围是 [ i , j ] [i,j] [i,j],则用 k k k 遍历 i i i j j j
    计算 A [ k ] + k A[k]+k A[k]+k 的最大值,记做 j 2 j2 j2 A [ k ] A[k] A[k] 表示当前位置最远能跳的距离。

  3. s t e p + + step++ step++;继续遍历 [ j + 1 , j 2 ] [j+1,j2] [j+1,j2]

每一个 s t e p step step 都有当前可跳到的范围,而当前的范围又确定下一个 s t e p step step 的可跳到的范围。每在一个 s t e p step step 遍历当前可跳的范围,确定下一跳的范围。这样总可以找到最短的 s t e p step step 跳到终点。每一个 s t e p step step 可跳的范围内,其 s t e p step step 的值都是相同。这个解题过程类似于广度优先搜索。,每一个 s t e p step step 可跳的范围为一层。

实现代码

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问题总结

虽然从代码上看有两层循环,但是我们分析执行过程可知只是从序列头跳到序列尾,时间复杂度只有 O ( n ) O(n) O(n)

该算法在每次跳跃中,都是尽量跳的更远,并记录 j 2 j2 j2——属于***贪心法***;也可以认为是从区间 [ i , j ] [i,j] [i,j] (若干结点)扩展下一层区间 [ j + 1 , j 2 ] [j+1,j2] [j+1,j2] (若干子结点)——属于***广度优先搜索***。

错位排列问题

问题描述

1 1 1 n n n 的全排列中,第 i i i 个数不是 i i i 的排列共有多少种?

问题分析

  • 假定 n n n 个数的错位排列数目为 d p [ n ] dp[n] dp[n]
  • 先考察数字 n n n 的放置方法:显然, n n n 可以放在从 1 1 1 n − 1 n-1 n1 的某个位置,共 n − 1 n-1 n1 种方法;假定放在了第 k k k 位。
  • 对于数字 k k k
    要么放置在第 n n n
    要么不放置在第 n n n 位。

这里写图片描述

数字k放置在第n位

相当于数字 k k k 和数字 n n n 交互位置后,其他 n − 2 n-2 n2 个数字做错位排列,因此有 d p [ n − 2 ] dp[n-2] dp[n2] 种方法。

这里写图片描述

数字k不放置在第n位

将数字 k k k 暂时更名为 n n n (这是可以做到的:因为真正的 n n n 已经放在第 k k k 位上,真正的 n n n 不再考虑之列),现在需要将 1 1 1 k − 1 k-1 k1 以及 k + 1 k+1 k+1 n n n n − 1 n-1 n1 个数放置在相应位置上,要求数字和位置不相同!显然是 n − 1 n-1 n1 个数的错位排列,有 d p [ n − 1 ] dp[n-1] dp[n1] 种方法。

这里写图片描述

错位排列递推公式

综上, d p [ n ] = ( n − 1 ) ∗ ( d p [ n − 1 ] + d p [ n − 2 ] ) dp[n]=(n-1)*(dp[n-1]+dp[n-2]) dp[n]=(n1)(dp[n1]+dp[n2]) ( n − 1 ) (n-1) (n1) 表示起始时,数字 n n n ( n − 1 ) (n-1) (n1) 个可放置的位置。

初值

只有 1 1 1 个数字,错位排列不存在, d p [ 1 ] = 0 dp[1]=0 dp[1]=0
只有 2 2 2 个数字,错位排列即交换排列, d p [ 2 ] = 1 dp[2]=1 dp[2]=1

则递推公式为:

这里写图片描述

实现代码

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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值