算法基础-->概率组合

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

求1的个数

问题描述

给定一个323232 位无符号整数NNN,求整数NNN 的二进制数中111 的个数。

问题分析

方法一

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

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

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

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

假定能够求出NNN 的高161616 位中111 的个数aaa 和低161616 位中111 的个数bbb,则a+ba+ba+b 即为所求。

为了节省空间,用一个323232 位整数保存aaabbb

  • 161616 位记录aaa,低161616 位记录bbb
  • (0xFFFF0000&N)(0xFFFF0000\verb'&'N)(0xFFFF0000&N) 筛选得到aaa
  • (0x0000FFFF&N)(0x0000FFFF\verb'&'N)(0x0000FFFF&N) 筛选得到bbb
  • (0xFFFF0000&N)+(0x0000FFFF&N)>>16(0xFFFF0000\verb'&' N) + (0x0000FFFF\verb'&'N)>>16(0xFFFF0000&N)+(0x0000FFFF&N)>>16

如何得到高161616 位和低161616 位中111 的个数aaabbb 呢?

  • 分治往往伴随着递归

递归过程:

  1. 如果二进制数NNN161616 位,则统计NNN 的***高 888 位*** 和***低 888 位***各自 111 的数目 aaabbb,而 a、ba、bab 用某一个161616 位数 XXX 存储,则使用0xFF00、0x00FF0xFF00、0x00FF0xFF000x00FF 分别于XXX 做***与操作***,筛选出 aaabbb;原问题中的数据是 323232 位,因此分别需要 2220xFF00/0x00FF0xFF00/0x00FF0xFF00/0x00FF,即0xFF00FF00/0x00FF00FF0xFF00FF00/0x00FF00FF0xFF00FF00/0x00FF00FF

  2. 如果二进制数是 888 位,则统计高 444 位和低 444 位各自111 的数目,使用 0xF0/0x0F0xF0/0x0F0xF0/0x0F 分别做与操作,筛选出高 444 位和低 444 位;原问题中的数据是 323232 位,则分别需要4440xF0/0x0F0xF0/0x0F0xF0/0x0F,即0xF0F0F0F0/0x0F0F0F0F0xF0F0F0F0/0x0F0F0F0F0xF0F0F0F0/0x0F0F0F0F

  3. 如果是 444 位则统计高 222 位和低 222 位各自 111 的数目,用 0xC/0x30xC/0x30xC/0x3 筛选(高222110011001100 十六进制表示 0xC0xC0xC,低 222001100110011 十六进制表示为 0x30x30x3);原问题中的数据是 323232 位,故各需要需要 8880xC/0x30xC/0x30xC/0x3 ,即 0xCCCCCCCC/0x333333330xCCCCCCCC/0x333333330xCCCCCCCC/0x33333333

  4. 如果是 222 位则统计高 111 位和低 111 位各自 111 的数目,用 0x2/0x10x2/0x10x2/0x1 筛选;原问题中的数据是323232 位,(因为在十六进制中,以四位为一个单位,则高 111 位为 101010101010 即为 0xA0xA0xA,需要 8880xA0xA0xA,同理低 111010101010101 即为0x50x50x5,也需要 888 个 )故各需要 8880xA/0x30xA/0x30xA/0x3,即为0xAAAAAAAA/0x333333330xAAAAAAAA/0x333333330xAAAAAAAA/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;
}

在采用HammingWeightHammingWeightHammingWeight 方法时,对于任何一个323232 位无符号整数NNN 只需要计算555 次运算即可。

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

跳跃问题

问题描述

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

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

问题分析

这里写图片描述

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

  1. 初始步数stepstepstep 赋值为000

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

  3. step++step++step++;继续遍历[j+1,j2][j+1,j2][j+1,j2]

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

实现代码

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)

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

错位排列问题

问题描述

111nnn 的全排列中,第iii 个数不是iii 的排列共有多少种?

问题分析

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

这里写图片描述

数字k放置在第n位

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

这里写图片描述

数字k不放置在第n位

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

这里写图片描述

错位排列递推公式

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

初值

只有111 个数字,错位排列不存在,dp[1]=0dp[1]=0dp[1]=0
只有222 个数字,错位排列即交换排列,dp[2]=1dp[2]=1dp[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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值