扩展算法4-滑动窗口法

以欧拉计划第八题讲解滑动窗口法

如果现在这里有1000个数字

731671765313306249192251196744265747423553491949349698352031277450632623957831801698480186947885184385861560789112949495459501737958331952853208805511125406987471585238630507156932909632952....(更多详见欧拉原题)

请你找出其中相邻五位最大的乘积(欧拉原题可不止这么点位置,我就是举个例子!后文附欧拉习题链接、原题、数据、源代码)

你会怎么计算?

我相信你一定已经有了思路:读入,然后暴力求解,然后输出

输入部分

但是首先我们遇到了第一个问题:该怎么输入呢?

上述相邻的数个数字

电脑该如何判断,第一个要输入的数字,是7,还是73,还是731,还是73167…呢?

我来提供一种思路:

将数字当作字符来处理。

char num[1010];
int len=0;
while(~scanf("%s",num+len))len=strlen(num);

这串代码首先声明了一个字符类型的数组

然后声明了一个变量len作为指针

每当输入一个数字后,将字符数组长度记为len

注:

num为首地址,即&num[0]

则num+len  等价于  &num[len]

然后向num[len]中输入一个字符,

再然后len记为字符长度,

再向num[len]输入一个字符,

len初始值为0,

也就是说,将这个1000位分别存储在num[0]-num[999]中,

举个例子,当len为50时,下一个该输入的为51位。

计算部分

我们的第一种思路为,暴力!

#include <iostream>
#include <string.h>
#include <cstdio>
using namespace std;
int main()
{
    char num[1010];
    int len=0;
    int ans=0;
    while(~scanf("%s",num+len))len=strlen(num);
    for(int i=1;i<1000;i++){
        int a=(num[i]-'0')*(num[i+1]-'0')*(num[i+2]-'0')*(num[i+3]-'0')*(num[i+4]-'0');
        if(a>ans)ans=a;
    }
    printf("%d",ans);
    return 0;
}

这显然是可以算的!

但是吧…他的时间复杂度是Olg(n^5)

太慢了

我们需要进行优化…

滑动窗口法

e61e42a16b48b52e03cc39d30e9b999

如果你要计算相邻四个数字的乘积,该怎么计算呢?

1 : a[1] * a[2] * a[3] * a[4]

2 : a[2] * a[3] * a[4] * a[5]

3 : a[3] * a[4] * a[5] * a[6]

很显然,我们计算了很多重复的步骤…

1 : a[1] *          a[2] * a[3] * a[4]

2 : a[2] * a[3] * a[4]           * a[5]

当我使ans从

a[1] * a[2] * a[3] * a[4]

变为

a[2] * a[3] * a[4] * a[5]时

其实不需要计算 a[2] * a[3] * a[4]

只要使ans/a[1]*a[5]就可以

这就是滑动窗口法的基本原理

需要注意的是

前后必须为逆运算,且每个值都不能等于零

当某一个值等于0时,这个式子就没办法按照滑动窗口法计算了。

那么,我们自然形成了对于0的判断思路:

1.当读取到0时,我们直接使窗口跳到0之后的第一位。

2.设置变量Zero来存储窗口中0的个数。只要Zero不等于0,窗口内的答案就无效。

6793b36b88ed57fdfe20c25e08caf61

欧拉第八题英文原题:https://projecteuler.net/problem=8

欧拉第八题中英对照:http://pe-cn.github.io/8/

/*************************************************************************
	> File Name: ol8.cpp
	> Author:ray 
	> Mail:1430134583@qq.com 
	> Created Time: Wed 05 Aug 2020 10:42:50 AM CST
 ************************************************************************/

#include <cstdio>
#include <string.h>
char num[1010];//开一个全局数组,节省空间
using namespace std;
int main()
{
    int len = 0;//声明一个整形变量,作为指针
    while (~scanf("%s", num + len))len = strlen(num);//向num中循环读入字符
    long long ans = 0, p = 1, zero =0;//声明一个长整型变量ans计算结果,声明一个长整型变量p初始化为1,声明一个长整型变量zero初始化为0
    //ans存储结果,p存储窗口当前的量,zero存储窗口中0的个数
    for (int i = 0; num[i]; i++) {//以num[i]为判断条件。因为num[]为一个字符数组,所以当某一位存储为0时,值是0的ASILL码,只有某一位为空时,才停止循环。
        num[i] -= '0';//使这一位由字符变成数字
        if (num[i]) p*= num[i];//若num[i]不为0,p=p*num[i](后一项进入窗口)
            else zero += 1;//若为0,Zero++,即窗口中0的个数加一
        if (i < 13) continue;//如果i<13 继续下一层循环(此时窗口还没存满数字,不进行出栈)
        if (num[i - 13]) p /= num[i - 13];//如果p[i-13]不是0,则p=p/num[1-13](前一项退出窗口)(加这个判断条件,是因为除数不能为0)
            else zero -= 1;//如果num[i-13]=0,令zero=-1,即窗口中0的个数减一
        					//zero代表的是当前窗口中0的个数
        if (zero == 0 && p > ans) ans = p;//如果zero=0(也就是窗口中没有0。此处写成zero<=0也行,不过没啥用,因为0的个数不会为负数)
        								//且ans<p,更新ans,使其变为p
    }
    printf("%lld\n",ans);//输出ans
    return 0;
}
最近的文章

扩展算法3-二分法

在编程中,查找是很重要的一环。比方说,有一个一百万位的、单调递增的数组,需要你找到某个值在第几位;比方说,有一个不规则函数,要你求出一个零点的位置;等等等。在这个时候,你总不能把这一百万都查找一遍吧?这个时候二分就派上用场了。那么二分是什么呢?你可以和同学做一个游戏,我们来模拟一下最基础的二分:让你…

继续阅读