CHAPTER_4 算法初步入门
4.7.1 打表
打表是一种空间换时间的技巧,一般指将所有可能用到的结果事先计算出来,这样在后面需要用到时直接查表即可。常见用法有如下几种:
(1)在程序中一次计算出所有可能用到的结果,之后的查询直接取这些结果
例如在某个问题中我们需要用到大量斐波那契数。在用到第n个斐波那契数F(n)时我们需要计算F(n),但若我们需要大量用到F(1)、F(2)、...、F(n),每次从头计算显然很浪费时间。我们可以提前将可能用到的F(n)都计算并存储与数组中,在需要用到只需查询即可。
(2)对一些感觉不会做的题目,先用暴力程序计算小范围数据的结果,然后找规律,或许能发现一些蛛丝马迹。
4.7.2 活用递推
有很多题目需要细心考虑过程中是否存在递推关系,如果能找到递推关系,就能使时间复杂度下降不少。
题目:
字符串APPAPT中包含了两个"PAT"单词,其中第一个PAT由第二位、第四位和第六位字母组成的;第二个PAT是由第三位、第四位和第六位组成的。
现给定字符串(只包含P、A、T三个字母),问一共可以形成多少个PAT?
输入格式:
输入只有一行,包含一个字符串,长度不超过10^5,只包含P、A、T三个字母
输出格式:
在一行中输出该字符串包含PAT的个数。由于结果可能比较大,因此只输出对1000000007取余数的结果。
输入样例:
APPAPT
输出样例:
2
思路:
对于一个确定位置的A来说,以它形成PAT的个数等于它左边P的个数乘以它右边T的个数。例如对于PPAT,它形成PAT的个数为2*1=2.。于是该问题转换为,对字符串中的每个A计算它左边P的个数与右边T的个数的乘积。之后把所有A的乘积相加。
那么有没有比较快获得每一位左边P个数的方法呢?当然有,我们可以设置一个数组leftNumP,记录每一位左边P的个数,接着从左到右遍历字符串,如果当前位i是P,那么令leftNumP[i]=leftNumP[i-1]+1;如果当前位i不是P,那么令leftNumP[i]=leftNumP[i-1]。
同样的,我们使用rightNumP记录每一位右边T的个数。从右到左遍历字符串,如果当前位i是T,那么令rightNumP[i-1]=rightNumP[i]+1;如果当前位i不是T,那么令rightNumP[i-1]=rightNumP[i]。
最后,我们遍历字符串。如果当前位是A,我们计算leftNumP[i]*rightNumP[i],并累加至总数。遍历完后将总数对1000000007取余数,输出。
参考代码:
#include<iostream>
#include<string>
using namespace std;
const int MOD=1000000007;
int main() {
string str;
cin>>str;
int len=str.size(),count=0; //获取字符串长,创建count用于存储最终结果
int* leftNumP=new int [len];
int* rightNumT=new int [len]; //创建和字符串等长的数组leftNumP、rightNumT
leftNumP[0]=(str[0]=='P')?1:0; //给leftNumP第一位赋值
for(int i=1;i<len;i++) {
if(str[i]=='P')
leftNumP[i]=leftNumP[i-1]+1;
else
leftNumP[i]=leftNumP[i-1];
} //按上述算法给leftNumP赋值
rightNumT[len-1]=(str[len-1]=='T')?1:0; //给rightNumT[最后一位赋值
for(int i=len-2;i>=0;i--) {
if(str[i]=='T')
rightNumT[i]=rightNumT[i+1]+1;
else
rightNumT[i]=rightNumT[i+1];
} //按上述算法给rightNumT赋值
for(int i=0;i<len;i++) {
if(str[i]=='A')
count+=leftNumP[i]*rightNumT[i];
} //遍历str计算最后结果
cout<<count%MOD<<endl;
return 0;
}
本文介绍算法入门中的两种技巧——打表与递推,并通过实例讲解如何运用这两种技巧来解决实际问题,提高算法效率。
226

被折叠的 条评论
为什么被折叠?



