打表
用空间换时间的技巧,一般指将所有可能需要用到的结果事先计算出来,这样后面需要用到时就可以直接查表获得。
常见用法:
1.在程序中一次性计算出所有需要用到的结果,之后的查询直接取这些结果
这是最常用到的用法,在一个需要查询大量Fibonacci数F(n)的问题中,显然每次从头开始计算是非常耗时的,对q次查询会产生nq的时间复杂度;如果进行预处理,即把所有Fibonacci数预先计算并存在数组中,那么每次查询就只需要O(1)的时间复杂度,q次查询只需要n+q的时间复杂度。
2.在程序b中分一次或多次计算出所有需要用到的结果,手工把结果写在程序A的数组中,然后在程序A中就可以直接使用这些结果。
3.对一些感觉不会做的题目,先用暴力程序计算小范围数据的结果,然后找规律,或许能发现一些蛛丝马迹。
活用递推
涉及序列的题目
【pat b1040】有几个pat
字符串APPAPT中包含了两个单词“PAT”,现给定字符串,问一共可以形成多少个PAT?
思路:
对一个确定位置的A来说,以它形成的PAT的个数等于它左边P的个数乘以他右边T的个数。对APPAPT的中间那个A来说,它左边有两个P,右边有一个T,计算它左边P的个数与它右边T的个数的乘积,然后把所有A的这个乘积相加就是答案。
获得每一位左边P的个数的方法:设定一个数组leftNumP,记录每一位左边P的个数,接着从左到右遍历字符串,如果当前位i是P,那么leftNumP[i]就等于leftNumP[i-1]加1;如果当前位i不是P,那么leftNumP[i]就等于leftNumP[i-1]。
以同样方法可计算出每一位右边T的个数。定义变量rightNumT,记录当前累计右边T的个数。
#include <cstdio>
#include <cstring>
const int MAXN=100010;
const int MOD=1000000007;
char str[MAXN]; //字符串
int leftNumP[MAXN]={0};
int main(){
//printf("请输入由P,A,T组成的字符串:");
gets(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]++;
}
}
int ans=0,rightNumT=0; //ans为答案,rightNumT记录右边T的个数
//从右往左遍历字符串,找T
for(int i=len-1;i>=0;i--){
if(str[i]=='T'){
rightNumT++;
} else if(str[i]=='A'){
ans=(ans+leftNumP[i]*rightNumT)%MOD;//累计乘积,左边的每一个P都要与T组合
}
}
//printf("所能组成的PAT个数为:");
printf("%d\n",ans);
return 0;
}
随机选择算法
问题:如何从一个无序的数组中求出第K大的数,假设数组中的数各不相同。例如,对数组{5,12,7,2,9,3}来说,第三大的数是5,第五大的数是9。
首先想到的是对数组排序,然后直接取出第K 个元素即可。但这样做需要O(nlogn)的时间复杂度,仍可优化。
随机选择算法,对任何输入都可以达到O(n)的期望时间复杂度。
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 pivot=A[left];
while(left<right){
while(left<right && A[right]>pivot) right--; //右指针往左移
A[left]=A[right]; //while条件不满足时,右边的数小,移到左边
while(left<right && A[left]<=pivot) left++; //左指针往右移
A[right]=A[left]; //while条件不满足时,左边的数大,移到右边
}
//while循环结束,left=right
A[left]=pivot; //pivot给left与right相遇的地方
return left; //返回相遇的下标
}
//随机选择算法,从A[left,right]中返回第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]; //找到第K大的数
if(K<M){
return randSelect(A,left,p-1,K); //往左侧找
}else{
return randSelect(A,p+1,right,K-M);
}
}