《算法笔记》学习笔记(12):其他高效技巧与算法

1.打表
打表是一种典型的空间换时间的技巧,一般指将所有可能需要用到的结果事先计算出来,这样后面需要用到时就可以直接查表获得。打表常见的用法有如下几种:
在程序中一次性计算出所有需要用到的结果,之后的查询直接取这些结果;
在程序B中分一次或多次计算出所有需要用到的结果,手工把结果写在程序A的数组中,然后在程序A中就可以直接使用这些结果;
对一些感觉不会做的题目,先用暴力程序计算小范围数据的结果,然后找规律,或许就能发现一些蛛丝马迹。

2.活用递推
1040 有几个PAT (25 分)
字符串 APPAPT 中包含了两个单词 PAT,其中第一个 PAT 是第 2 位§,第 4 位(A),第 6 位(T);第二个 PAT 是第 3 位§,第 4 位(A),第 6 位(T)。

现给定字符串,问一共可以形成多少个 PAT?

输入格式:
输入只有一行,包含一个字符串,长度不超过10
​5
​​ ,只包含 P、A、T 三种字母。

输出格式:
在一行中输出给定字符串中包含多少个 PAT。由于结果可能比较大,只输出对 1000000007 取余数的结果。

输入样例:
APPAPT
输出样例:
2

直接暴力会超时。换个角度,对一个确定位置的A来说,PAT的个数等于左侧的P个数乘以右侧的T个数,可以设定一个数组leftNumP记录每一位左侧P的个数。之后再设一个数rightNumT,从右向左遍历,碰到T时+1,碰到A时计算ans。
示例程序

const int maxn = 100;
const int MOD = 1000000007;
char str[maxn];//字符串
int leftNumP[maxn] = { 0 };//每一位左边含P的个数
int main()
{
	gets_s(str);//读字符串
	int len = strlen(str);//计算长度
	for (int i = 0; i < len; i++)
	{
		if (i > 0)//如果不是第零位
		{
			leftNumP[i] = leftNumP[i - 1];//继承上一位的结果
		}
		if (str[i] == 'P')
		{
			leftNumP[i]++;//如果这一位是P再加1
		}
	}
	int ans = 0, rightNumT = 0;
	for (int i = len - 1; i >= 0; i--)//从末位向前计数
	{
		if (str[i] == 'T')
		{
			rightNumT++;//如果当前位是T,则加1
		}
		else if (str[i] == 'A')
		{
			ans = (ans + leftNumP[i] * rightNumT) % MOD;//如果碰到A则计算ans
		}
	}
	printf("%d\n", ans);
	return  0;
}

输出
在这里插入图片描述
3.随机选择算法
如何从一个无序的数组中求出第K大的数?最直接的想法是对数组拍一下序,但这样的做法需要O(nlogn)的时间复杂度,而随机选择算法对任何输入都可以达到O(n)的复杂度。
随机选择算法原理类似于随即快速排序算法。当A[left,right]执行一次randPartition的函数后,主元左侧的元素个数是确定的,且他们都小于主元。假设此时主元是A[p],那么A[p]就是A[left,right]第p-left+1大的数。不妨令M表示p-left+1,那么若K==M,说明第K大的数就是主元A[p];如果K<M成立,就说明第K大的数在主元左侧,往左递归即可,反之向右递归,以left=right作为递归边界,返回A[left]

//随机选择算法
void swap(int& a, int& b)
{
	int temp = a;
	a = b;
	b = temp;
}
int randPartition(int A[], int left, int right)
{
	int p = (round(1.0 * rand() / RAND_MAX * (right - left) + left));
	swap(A[p], A[left]);
	int temp = A[left];//将A[left]存入临时变量temp
	while (left < right)//left与right不相遇
	{
		while (left<right && A[right]>temp)right--;//不断左移right直到找到一个小于选定值的值
		A[left] = A[right];//将A[right]移到A[left]
		while (left < right && A[left] <= temp)left++;//不断右移left直到找到一个大于等于选定值的值
		A[right] = A[left];//将A[left]移到A[right]
	}
	A[left] = temp;//temp归位
	return left;//返回相遇的下标
}
//随机选择找第k大的数
int randSelect(int A[], int left, int right, int K)
{
	if (left == right)return A[left];
	int p = randPartition(A, left, right);
	int M = p - left + 1;
	if (K == M)return A[p];
	if (K < M)
	{
		return randSelect(A, left, p - 1, K);
	}
	else
	{
		return randSelect(A, p + 1, right, K - M);
	}
}
int main()
{
	int a[] = { 5,12,7,2,9,3 };
	int ans=randSelect(a, 0, 5, 3);
	printf("%d\n", ans);
	return 0;
}

运行结果
在这里插入图片描述
给定一个由整数组成的集合,集合中的整数各不相同,现在要将它分为两个子集合,使得这两个子集合的并为原集合、交为空集,同时在两个子集合的元素个数n1与n2之差的绝对值|n1-n2|尽可能小的前提下,要求它们各自的元素之和S1与S2之差的绝对值|S1-S2|大
可以使用上面用到的随机选择算法,本质就是找第n/2大的元素

//随机选择算法
void swap(int& a, int& b)
{
	int temp = a;
	a = b;
	b = temp;
}
int randPartition(int A[], int left, int right)
{
	int p = (round(1.0 * rand() / RAND_MAX * (right - left) + left));
	swap(A[p], A[left]);
	int temp = A[left];//将A[left]存入临时变量temp
	while (left < right)//left与right不相遇
	{
		while (left<right && A[right]>temp)right--;//不断左移right直到找到一个小于选定值的值
		A[left] = A[right];//将A[right]移到A[left]
		while (left < right && A[left] <= temp)left++;//不断右移left直到找到一个大于等于选定值的值
		A[right] = A[left];//将A[left]移到A[right]
	}
	A[left] = temp;//temp归位
	return left;//返回相遇的下标
}
//随机选择找第k大的数
int randSelect(int A[], int left, int right, int K)
{
	if (left == right)return A[left];
	int p = randPartition(A, left, right);
	int M = p - left + 1;
	if (K == M)return A[p];
	if (K < M)
	{
		return randSelect(A, left, p - 1, K);
	}
	else
	{
		return randSelect(A, p + 1, right, K - M);
	}
}

const int maxn = 100010;
int A[maxn], n;
int main()
{
	srand((unsigned)time(NULL));
	int sum = 0, sum1 = 0;
	scanf("%d", &n);
	for (int i = 0; i < n; i++)
	{
		scanf("%d", &A[i]);
		sum += A[i];
	}
	randSelect(A, 0, n - 1, n / 2);
	for (int i = 0; i < n / 2; i++)
	{
		sum1 += A[i];
	}
	printf("子集1为:\n");
	for (int i = 0; i < n / 2; i++)
	{
		printf("%d ", A[i]);
	}
	printf("\n子集2为:\n");
	for (int i = n/2; i < n; i++)
	{
		printf("%d ", A[i]);
	}
	printf("\n|S1-S2|=%d\n", (sum - sum1) - sum1);
	return 0;
}

运行结果
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值