题目:

给定一整数数列,问数列有多少个子序列是等差数列。

即对于包含N个数的数列A,A(0),A(1),……,A(N-1),有多少组(P(0),P(1),……,P(k))满足0<=P(0)<P(1)<……<P(k)<N,且A(P(0)),A(P(1)),……,A(P(k))为等差数列。

等差数列至少包含3个数,故必有k>=2,同时等差数列相邻两个数的差都是一样的,即A(P(1))-A(P(0) = A(P(2))-A(P(1)) = …… = A(P(k))-A(P(k-1)) = d,d被称为公差。

输入保证N个整数的取值范围均为-2^31 ~ 2^31-1,并且0<=N<=1000,同时保证输出小于2^31-1。

Example:

输入: [2, 4, 6, 8, 10]

输出: 7

题解:

来源:九章算法公众号(侵删)。

时间复杂度为O(N^2)的动态规划:

Ⅰ.我们令f(i,d)表示以A(i)结尾,公差为d的等差子序列的个数,这里我们允许存在长度为2的等差子序列(所以对于数列中任意两个数组成的子序列,我们都暂时认为其为等差子序列)。

那么对于一对(i,j),j<i,A(i)-A(j)=d,对于所有以A(j)结尾,公差为d的等差子序列来说,后面再跟上A(i)之后还是公差为d的等差子序列,但变成了以A(i)结尾,再加上一对(A(j),A(i)),就得到了所有形如(……,A(j),A(i))的等差子序列。

换言之,j将对f(i,d)贡献f(j,d)+1。故f(i,d)等于所有满足j<i且A(i)-A(j)=d的(f(j,d)+1)之和。

Ⅱ.一个问题是d的范围其实很大(-2^32+1 ~ 2^32-1),如果要对所有可能的d进行枚举,那么在时间上和空间上都是受不了的。

虽然d的取值范围很大,但是对于N个数来说,两两之差最多只可能有N(N-1)/2种;而对于1个数A(i)来说,只需考虑所有小于i的j所产生的d=A(i)-A(j),最多有i种可能。

所以,对于每一个i,可以用一个HashMap来存储键值对(d,f(i,d))。另一个问题是,我们在计算f(i,d)时,允许等差子序列长度为2(这一点是必要的,因为没有长度为2的序列的话,就没法在其末尾加上一个数得到更长的子序列),但答案要求的是所有长度至少为3的等差子序列的个数。

解决这个问题的方法有很多:在计算f(i,d)时,f(j,d)所表示的所有子序列长度都至少为2,在末尾加上A(i)之后,就成了满足条件的等差子序列,故可以在计算f(i,d)的同时累加所有f(j,d),最后即可得到正确的答案(这种写法比较简洁但不太直观);

也有一种比较容易理解的方法,那就是对所有f(i,d)之和,即所有长度至少为2的等差子序列的个数,减去长度为2的等差子序列的个数,而由于任意两个数都构成长为2的等差子序列,所以其个数为N(N-1)/2,两者相减得到的差即为正确答案。

Ⅲ.总结一下这个动态规划算法:对于每个i=0,1,2,……,N-1,创建一个HashMap存储键值对(d,f(i,d)),f(i,d)的初值为0,枚举j<i,d=A(i)-A(j),则f(i,d)增加f(j,d)+1,同时对答案增加f(j,d)。计算完所有的i之后即可得到答案。

一个小细节是,如果d不在[-2^31+1 , 2^31-1]的范围内,那么以这个d为公差的数列长度不可能是3或3以上,故对于d在这个范围外的情况可以直接跳过。

利用HashMap存取f(i,d),f(j,d)的复杂度为O(1),i,j枚举的复杂度为O(N^2),故总的时间复杂度为O(N^2)。

Solution 1 :

int getNum(const vector<int> &nums) {
if (nums.size() < ) {
return ;
}
vector<unordered_map<int, int>> map(nums.size());
int res = ;
for (int i = ; i < nums.size(); ++i){
for (int j = ; j < i; ++j) {
if (abs((long)nums[i] - nums[j]) > INT_MAX) {
continue;
}
int d = nums[i] - nums[j];
int map_i_d, map_j_d;
map_i_d = map[i].count(d) ? map[i][d] : ;
map_j_d = map[j].count(d) ? map[j][d] : ;
map_i_d += map_j_d + ;
map[i][d] = map_i_d;
res += map_j_d;
}
}
return res;
}

事实上,确定一个等差数列只需要三个数,一个是等差数列的长度L,还有两个是等差数列的最后两个数(也可以是任意两个中间的下标确定的数)。

记最后一个为E1,最后第二个为E2,则得公差d=E1-E2,通过公差可以推出等差数列中其余的数。

一个以E2,E1,结尾的等差数列,在末尾加上一个数E1+d后仍然是等差数列。于是我们可以使用动态规划求解:令g(i,j)为以A(j),A(i)结尾的等差子序列的个数(j<i),(即形如(……,A(j),A(i))的等差数列的个数),然后我们可以通过枚举倒数第三个数A(k)来统计g(i,j)。

对于形如(……,A(k),A(j))的等差子序列来说,如果有A(i)-A(j)=A(j)-A(k),那么对应的(……,A(k),A(j),A(i))也为等差子序列,同时由于(A(k),A(j))长度为2,不计入g(j,k)的中,但(A(k),A(j),A(i))应计入g(i,j)中,故将g(j,k)计算入g(i,j)时还要额外加1。

于是我们有g(i,j)=Σ(g(j,k)+1),其中k满足k<j且A(i)-A(j)=A(j)-A(k)。将所有得到的g(i,j)相加即可得到所有等差子序列的个数。这个算法的时间复杂度为O(N^3),考虑到N的范围,这样的时间复杂度可以接受,而且与上面讲的算法相比简洁许多。

Solution 2 :

int getAns(const vector<int> &nums) {
if (nums.size() < )
return ;
int n = nums.size();
vector<vector<int>> v(n, vector<int>(n, ));
int res = ;
for (int i = ; i < n; ++i) {
for (int j = ; j < i; ++j) {
for (int k = ; k < j; ++k) {
if (nums[i] - nums[j] == nums[j] - nums[k]) {
v[i][j] = v[j][k] + ;
res += v[i][j];
}
}
}
}
return res;
}

【Facebook】等差子序列个数的更多相关文章

  1. BZOJ 2124: 等差子序列

    Sol 线段树+Hash. 首先暴力 等差子序列至少3项就可以了,就枚举中项,枚举公差就可以了,只需要一个数在中项前出现,另一个数在中项前没出现过就可以了.复杂度 \(O(n^2)\) 然后我想了一个 ...

  2. [bzoj2124]等差子序列(hash+树状数组)

    我又来更博啦     2124: 等差子序列 Time Limit: 3 Sec  Memory Limit: 259 MBSubmit: 941  Solved: 348[Submit][Statu ...

  3. codevs 1283 等差子序列

    http://codevs.cn/problem/1283/ 题目描述 Description 给一个 1 到 N 的排列{Ai},询问是否存在 1<=p1<p2<p3<p4& ...

  4. P2757 [国家集训队]等差子序列

    P2757 [国家集训队]等差子序列 题目传送门 推荐一篇好题解 此题要求我们在一个序列中找出一个等差子序列. 显然,我们只需要考虑子序列长度len=3的情况,因为在长度为4的子序列中必定有一个长度为 ...

  5. [BZOJ2124]等差子序列/[CF452F]Permutation

    [BZOJ2124]等差子序列/[CF452F]Permutation 题目大意: 一个\(1\sim n\)的排列\(A_{1\sim n}\),询问是否存在\(i,j(i<j)\),使得\( ...

  6. bzoj 2124 等差子序列 (线段树维护hash)

    2124: 等差子序列 Time Limit: 3 Sec  Memory Limit: 259 MBSubmit: 1922  Solved: 714[Submit][Status][Discuss ...

  7. bzoj 2124 等差子序列 树状数组维护hash+回文串

    等差子序列 Time Limit: 3 Sec  Memory Limit: 259 MBSubmit: 1919  Solved: 713[Submit][Status][Discuss] Desc ...

  8. 【bzoj2124】等差子序列 STL-bitset

    题目描述 给一个1到N的排列{Ai},询问是否存在1<=p1<p2<p3<p4<p5<…<pLen<=N (Len>=3),使得Ap1,Ap2,A ...

  9. 等差子序列(sequence)

    等差子序列(sequence) 题目描述 给一个1到N的排列{Ai},询问是否存在1<= p1 < p2 < p3 < p4 < p5 < - < pLen ...

随机推荐

  1. C#数组 多个集合和数组的操作(合并,去重,拆分,判断)

    http://www.cnblogs.com/liguanghui/archive/2011/11/09/2242309.html http://www.cnblogs.com/dreamszx/ar ...

  2. php之 人员的权限管理

    1.想好权限管理的作用? 2.有什么权限内容? 3.既然有权限管理那么就会有管理员? 4.登录后每个人员的界面会是不一样的? 一.想好这个权限是什么? 就做一个就像是vip的功能,普通用户和vip用户 ...

  3. 如何使用EasyNVR+CDN突破萤石云在直播客户端数量上的限制,做到低成本高性价比的直播

    恰逢五一假期,有以为来自内蒙的用户向我电话咨询,大概的场景是这样的: 目前用户使用的是全套的海康IPC和NVR设备: 海康NVR设备通过设置萤石云平台,由萤石云对外提供直播服务: 萤石云对单个摄像机同 ...

  4. css3 transition效果

    <meta charset="UTF-8"> <style> .btn { display: inline-block; font-size: 12px; ...

  5. 为system对象添加扩展方法

    ////扩展方法类:必须为非嵌套,非泛型的静态类 public static class DatetimeEx { //通过this声明扩展的类,这里给DateTime类扩展一个Show方法,只有一个 ...

  6. NOIP考前划水

    NOIP考前划水 君指先跃动の光は.私の一生不変の信仰に.唯私の超電磁砲永世生き! 要开始背配置了? 3行不谢. (setq c-default-style "awk") (glo ...

  7. 超轻量级、高性能C日志库--EasyLogger

    [ 声明:版权全部,欢迎转载.请勿用于商业用途. 联系信箱:armink.ztl@gmail.com] EasyLogger 1. 介绍 EasyLogger 是一款超轻量级(ROM<1.6K, ...

  8. Java从零开始 第一天

    java是一门优秀的编程语言.java也有很多优点.从零开始.这是第一天,不是为什么,而是让自己学的更多. 1.下载JDK 在浏览器输http://www.oracle.com/index.html. ...

  9. 寻找第K大 网易2016实习研发工程师编程题

    有一个整数数组,请你根据快速排序的思路,找出数组中第K大的数. 给定一个整数数组a,同时给定它的大小n和要找的K(K在1到n之间),请返回第K大的数,保证答案存在. 测试样例: [1,3,5,2,2] ...

  10. Excel控制IE

    ---恢复内容开始--- 1.初始化and连接http网页 Set ie = CreateObject("InternetExplorer.Application") ie.Vis ...