由补题引申出的对memset函数时间复杂度的思考

原题:
D - Test Cases
You want to prepare test cases for a problem with the following description:
“Given an array of n positive integers, and a number of queries. Each query will ask about a range [l, r] (1 ≤ l ≤ r ≤ n), where each of the values between index l and index r (both inclusive) occurs an even number of times except one value. The answer to each query is the value that occurs an odd number of times in the given range.”
You have generated an array of n positive integers, you need to know the number of valid queries you can ask on this array. A query is valid if it has an answer, and that answer is unique.

Input
The first line of input contains a single integer T (1 ≤ T ≤ 64), the number of test cases.
The first line of each test case contains a single integer n (1 ≤ n ≤ 5000), the size of the array.
The next line contains n space-separated integers, a1, a2, …, an (1 ≤ ai ≤ 106), the values of the array.
The total sum of n overall test cases doesn’t exceed 72000.

Output
For each test case, print a single line with the number of valid queries you can ask.

Example:

Input
1
7
9 1 1 2 1 9 9

Output
12

一道题目挺短的题,但是题意非常难懂,我读了接近一个小时题,查了无数单词,最后还是没读懂题意。比完赛补题的时候直接用百度翻译,然而也没翻译出来个所以然来,最后看了下别人博客的题意才理解。

题意:输入数据的组数,和每组有多少个数据,然后输入这个数组,要求合法区间的个数(合法区间是指这个区间里出现奇数次的数存在且只有一个)

思路的话就比较简单了 数据比较小5000,直接两个for循环模拟范围l和r,然后记录当前元素出现的次数,然后看一下是否只有一个元素出现次数是奇数就ok了。

我原本写的代码:

#include <bits/stdc++.h>

using namespace std;

int book[1000010];//用来记录元素出现的次数

int main()
{
    int i,j,n,t,a[100010],sum,result;
    freopen("cases.in","r",stdin);
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d",&n);
        for(i=0; i<n; i++)
            scanf("%d",&a[i]);
        result=0;
        for(i=0; i<n; i++)
        {
            sum=0;//sum代表当前区间内奇数元素的个数
            memset(book,0,sizeof(book));
            for(j=i; j<n; j++)
            {
                book[a[j]]++;//出现次数+1
                if(book[a[j]]%2!=0)//如果这个元素出现次数是奇数的话sum++
                    sum++;
                else//否则sum--,例如,如果刚开始进来一个1那么就只有1这个元素出现了奇数次(1次),然后又进来一个1的话,那么奇数的个数就应该-1(因为1这个元素出现两次了)
                    sum--;
                if(sum==1)//如果这个区间内只有一个元素出现奇数次
                    result++;//总次数+1
            }
        }
        printf("%d\n",result);//最后输出总次数
    }
    return 0;
}

这个思路几乎是水到渠成的,写的时候也没什么大问题,但是交的时候就出问题了,TLE(超时)了,到这个时候我就发现不太对劲,因为我的book定义了1e6那么大,如果外面再嵌套一个5e3的for循环估计是要超时的,于是我在想是不是memset函数时间复杂度的问题。最后我发现其实不需要每次都把book数组全部清0,只需要把我们下次用到的book数组清0就好了,于是我改了一下代码。

新代码:

#include <bits/stdc++.h>

using namespace std;

int book[1000010];

int main()
{
    int i,j,n,t,a[100010],sum,result;
    freopen("cases.in","r",stdin);
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d",&n);
        for(i=0; i<n; i++)
            scanf("%d",&a[i]);
        result=0;
        memset(book,0,sizeof(book));//在刚开始清空为0
        for(i=0; i<n; i++)
        {
            sum=0;
            for(j=i; j<n; j++)
            {
                book[a[j]]++;
                if(book[a[j]]%2!=0)
                    sum++;
                else
                    sum--;
                if(sum==1)
                    result++;
            }
            for(j=i; j<n; j++)//为下一次区间循环清0
                book[a[j]]=0;
        }
        printf("%d\n",result);
    }
    return 0;
}

这样一来就不会超时了,这个题的时间限制是2000ms,我用第一个代码交了两次都是超时,然后我改进后的代码只需要373ms就ac了,由此可见有时候memset也并不是那么好用,虽然我隐约知道memset这个函数的时间复杂度几乎是O(n),但是由于memset不需要写for循环,同时只占有一行,所以经常被使用,但其实这个函数的复杂度还是蛮高的,所以在做一些题的时候我们在检查代码的时候不妨把memset函数看成O(n)的复杂度然后检查代码是否超时,这样也许能帮我们减少一次TLE,最后说一下我个人认为在什么时候用for清空数组,在什么时候用memset清空数组。
例如:

for(i=0;i<n;i++)
    {
      a[i]=0;
      b[i]=0;
      c[i]=0;
      d[i]=0;
    }
    memset(a,0,sizeof(a));
    memset(b,0,sizeof(b));
    memset(c,0,sizeof(c));
    memset(d,0,sizeof(d));

在上面两个例子中,如果需要清空或者处理的数组不止一个的话用第一种方法(for循环)处理比较好,只需要跑1个for循环,同时数组可以赋值的数有很多,memset理论上只能给数组赋值-1或0,如果赋其他值的话会不如我们所愿(不会赋成你希望的那个值),虽然我个人觉得这两种方法的时间复杂度应该是一样的,但实际上第二种方法(memset赋值法)更容易超时,虽然我不太清楚内部的原理,但还是记录下来,以后做题的时候斟酌选择吧。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值