刚开始学习算法,有了一点感悟,记录。
现有逻辑思想,再把逻辑思想编译成c代码,这就是编程。要相信,只要逻辑缜密,编译出的c代码就一定正确,至于c代码的细节到不是必须要的,甚至这些细节都是思维陷阱,不能多多考虑。更重要的是对题目的逻辑思考的训练和养成!
我一开始做题,习惯从1开始思考问题,简单的还行,一旦到复杂的问题,往往做不出来,而且看ac的代码还很困难,这几天在acWing的学习,让我认识到,从头开始思考的模式是一大主因。
为什么?
从中间想,就有“上一个”的概念,就可以利用之前扫描过的信息。想好中间一般过程后,再处理一下“开头”和“结尾”需要特判的地方,就很容易把一些复杂问题想通透。
例1:先想中间,开头特判的例子,比如去除数组重复元素的代码:
//设a数组已经有数据,b数组为空数组;
int m=0;
sort(a,a+n);
for(int i=1;i<=n;i++){
if(i==1 || a[i]!=a[i-1]){
b[++m]=a[i];
}
}
例题2:前缀和,a是一个整型数组,长度n,b是a的前缀和。
for(int i=1;i<=n;i++){
b[i]=b[i-1]+a[i];
}
b数组当前值=上一个值+a[i],然后考虑0时没有下一个,于是从1开始,完美解决,这样想就很简单。
我现在是这么思考的
- 从中间的当前量开始思考,这样就有了当前量之前的,和之后的量。
- 开头需要特判的,手动填入,像dp问题,还有上面例子的代码。
- 结尾是无限多的,尽量简单的证明一下,而不是钻入无限的坑里。
这里在说下第3步什么意思。
我从中间量开始思考,有的题目有无限多种可能时,往往会想不通,因为我模拟出一种情况,不代表其他情况也可以,从心里上怀疑自己代码的正确性。如果用数学的知识加以简单的证明,就可以大大消除这种疑惑感。
例子1:在最大公约数这个例子中:
int gcd(int a,int b){
return b==0?a:gcd(b,a%b);
}
初学者看这个代码容易蒙,为什么b==0时,就返回a?
,从中间思考就很容易打开局面:当前的b==0
,代表上一次的a%b==0
,也就是上一次计算中,b就是那个约数,而上一次计算的b,就是当前的a呀!
这里还有一个结尾无限多的问题。因为a,b是任一2个数,怎么知道b最后一定得到0?,根据数论知识,2个整数辗转求余,必然越来越小,最终结果一定会在0结束。
例子2:在一个数组中,找出2个数的和为t:
一个从中间开始思考的思路是:如果当前元素a[i]的前面存在元素t-a[i],那么元素a[i]和t-a[i]就是要找到答案。
#include <cstdio>
using namespace std;
int main(){
int a[9]={1,2,9,3,5,6,7,8,9},t=8;
for(int i=0;i<9;i++){
for(int j=0;j<i;j++){
if(t-a[i]==a[j]){
printf("%d,%d\n",a[i],a[j]);
return 0;
}
}
}
printf("no find\n");
return 0;
}
输出:
5,3
这个代码不是最优的,但能简单的说明中间思考的过程。连最后的输出都说明,我的思路历程。5是当前元素,我在5之前又找到8-5的元素3,这样就找到答案了!
例子3:计算0-9的阶乘
void fab() //计算0~9的阶乘
{
f[0] = 1;
for (int i = 1; i <= 9; i++)
{
f[i] = i * f[i - 1];
}
}
例子4:朴素质数筛
思路:如果当前数i没有被删除,那么它一定不是2到i-1中任一数的倍数,这符合质数定义。故该数是质数。再特判一下开头,2显然符合,思路成立。
int primes[N],cnt,n;
bool st[N];
for(int i=2;i<=n;i++){
if(!st[i]) primes[cnt++]=i;
for(int j=i+i;j<=n;j+=i) st[j]=true;
}