Before the Beginning

本文为 Clouder 原创文章,原文链接为 https://www.codein.icu/gci-subarray/,转载时请将本段放在文章开头显眼处。如进行了二次创作,请明确标明。

前言

在颓废地水 Telegram 的时候,在 Codeforces 群里看到有人发了张谷歌面试的题的图,还是有那么一些意思的,向神犇求助后有所收获,写一篇题解。

题目难度不大,如何优雅地解决才是问题。

题面

给定一个无序数组 \(A\),长度为 \(N\),元素皆为非负整数,要求找到一段连续的子序列使得其和为 \(S\)。

思路

暴力的思路非常简单,枚举左右端点乱搞就是了。复杂度大概是 \(O(n^3)\) 的。

考虑稍加优化,预处理出前缀和,依然枚举左右端点,复杂度为 \(O(n^2)\)。

这是最直观的想法了,然而要求复杂度为 \(O(n)\),就必须找到更优的算法。

哈希表法

既然有了前缀和,那么这一段子序列可以用数学语言来表示一下:

\(S = s_i - s_j(j \leq i)\)

其中 \(s\) 代表前缀和。

稍加变换,就可以变为:

\(s_i - S = s_j(j \leq i)\)

问题转化为是否存在 \(j \in [1,i]\) 使得 \(s_j = s_i - S\)。

那么可以顺着扫一遍,判断之前是否有 \(s_j = s_i - S\),再将 \(s_i\) 的值记录下来。

伪代码:

for(int i = 1;i<=n;++i)
{
s[i] = s[i - 1] + a[i];
if(map[s[i] - S] != 0)
return make_pair(map[s[i] - S] + 1,i);
map[s[i]] = i;
}

那么复杂度的瓶颈就在于这个 map 如何实现了。使用红黑树可以做到稳定的 \(O(n\log n)\),而使用哈希表可以做到 \(O(n)\)。

然而哈希表的复杂度相当玄学,并且在元素值域过大时表现并不好。

有没有更稳定的、优雅的解决方法呢?

双指针扫描法

这是与神犇讨论后产生的解法,笔者认为相当优雅,并且顺路膜拜了神犇。

双指针扫描发,或者说对撞指针法?网上的资料较少,我只能大致讲一下。

拿经典的两数之和来举例子吧。

首先保证数组有序,要求找到两个数和为定值。

那么初始化左指针为数组开头,右指针为数组末尾。

判断两数相加,若大于目标值,则右指针左移。若小于目标值,则左指针右移。

那么两个指针重合时终止。很容易证明复杂度为 \(O(n)\)。

相信这个还是很容易理解的。

那么这道题,只是将两数之和变成了两数之差,也可以使用相类似的双指针法。

要求:

\(S = s_i - s_j(j \leq i)\)

先预处理出前缀和数组,由于元素都是非负整数,前缀和数组天然单调递增。

发现右指针右移时单调递增,左指针右移时单调递减,因此满足了单调性。

如果空数组也是可选的,那么右指针初始和左指针位置相同。

伪代码:

int lp = 0,rp = 0;
while(lp <= rp && lp >= 0 && rp <= n)
{
if(s[rp] - s[lp] == S)
return make_pair(lp + 1,rp);
if(s[rp] - s[lp] < S)
++rp;
else
++lp;
}

那么复杂度就是相当稳定的 \(O(n)\)了。

双指针扫描法证明

至于双指针法的正确性,感性理解很容易,但严谨证明,笔者觉得还是有些难度的。(当然是笔者太弱了)

在借鉴了 chend大佬的两数之和正确性证明 后,笔者也尝试自证一下。

使用数学归纳法证明算法运行过程中 \(\forall a \in [0,L],b \in [L+1,R]\),\(s_b - s_a \neq S\)。

  1. 初始时,不考虑空数组的情况,从 \(L = 0,R = 1\) 开始,若成立则算法退出,否则命题成立。
  2. 假定 \(\forall a \in [0,L],b \in [L+1,R]\) 中命题已成立,

    欲证 \(\forall a \in [0,L+1],b \in [L+2,R]\) 中命题成立,

    若 \(s_{R} - s_{L+1} = S\) 则算法结束,因此要证明命题,

    即证 \(\forall b \in [L+2,R]\) 都有 \(s_b - s_{L+1} \neq S\)。

    使用反证法证明,假定 \(\forall b \in [L+2,R]\) 有 \(s_b - s_{L+1} = S\),若 \(b = R\) 则算法已结束,因此 \(b \in [L+2,R - 1]\)。

    那么由单调性,有 \(s_b - s_{L} >= S\),且如果取等号则算法在先前已结束,因此 \(s_b - s_L > S\)。

    根据定义,当 \(s_b - s_L > S\) 时,右指针将会固定在 \(b\) 的正确位置,左指针会直接移动到 \(L+1\),而右指针不会到达当前的 \(R\) 的位置,矛盾。

    因此在算法运行过程中,若 \(a \in [0,L],b \in[L+1,R]\) 中命题成立,则\(a \in [0,L+1],b \in [L+2,R]\) 中命题成立。
  3. 同理可证明 \(a \in [0,L],b \in [L+1,R+1]\) 中命题成立。

那么在算法运行过程中,根据定义移动指针可始终保证命题成立,不会漏掉 \(s_b - s_a = S\) 的情况。

由于笔者水平问题,证明并不严谨,读者可看大佬原文自行证明。

结语

做题容易,优雅地切题难,切完要证更难啊……

对指点笔者的两位神犇表达膜拜之情。

附上代码包,包含两种方法和数据生成器、检验器和对拍器。

为了实现方便,哈希表使用了 map 容器来代替。

蓝奏云下载

[GoogleInterview]连续子序列问题的更多相关文章

  1. lintcode 最长上升连续子序列 II(二维最长上升连续序列)

    题目链接:http://www.lintcode.com/zh-cn/problem/longest-increasing-continuous-subsequence-ii/ 最长上升连续子序列 I ...

  2. 最大连续子序列乘积(DP)

    题目来源:小米手机2013年校园招聘笔试题 题目描述: 给定一个浮点数序列(可能有正数.0和负数),求出一个最大的连续子序列乘积. 输入: 输入可能包含多个测试样例.每个测试样例的第一行仅包含正整数 ...

  3. DP专题训练之HDU 1231 最大连续子序列

    Description 给定K个整数的序列{ N1, N2, ..., NK },其任意连续子序列可表示为{ Ni, Ni+1, ..., Nj },其中 1 <= i <= j < ...

  4. HDU 1231 最大连续子序列(水题)

    题目链接: 传送门 最大连续子序列 Time Limit: 1000MS     Memory Limit: 32768 K Description 给定K个整数的序列{ N1, N2, ..., N ...

  5. HDU-1231 简单dp,连续子序列最大和,水

    1.HDU-1231 2.链接:http://acm.hdu.edu.cn/showproblem.php?pid=1231 3.总结:水 题意:连续子序列最大和 #include<iostre ...

  6. HDU 1231:最大连续子序列 解题报告

    第一次写博客, 自己总结写出了一道题感觉值得保存. 自己总结的规律:求最大连续子序列, 可以先求包括第N项在内的前N项最大值, (因为每一项都求过后, 前N项最大值最大的那一个元素所连续的序列即为最大 ...

  7. [ACM_其他] 总和不小于S的连续子序列的长度的最小值——尺缩法

    Description: 给定长度为n的整数数列,A[0],A[1],A[2]….A[n-1]以及整数S,求出总和不小于S的连续子序列的长度的最小值.如果解不存在,则输出0. Input: 输入数据有 ...

  8. UVa 108 - Maximum Sum(最大连续子序列)

    题目来源:https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=3&pa ...

  9. HDU 1231 最大连续子序列 &&HDU 1003Max Sum (区间dp问题)

    C - 最大连续子序列 Time Limit:1000MS     Memory Limit:32768KB     64bit IO Format:%I64d & %I64u Submit ...

随机推荐

  1. JavaScript的技巧45招

    JavaScript奇技淫巧45招 来自仲老师的分享: 原文地址[http://chensd.com/2015-01/45-useful-javascript-tips-tricks-and-best ...

  2. kafka connector

    Kafka Connect 是一种用于在 Kafka 和其他系统之间可扩展的.可靠的的流式传输数据的工具.它使得能偶快速定义将大量数据集合移入和移除 kafka 连接器变得简单. kafka conn ...

  3. mysql事务隔离分析

    首先说明下,这里主要内容为整理总结网络搜索的零散信息. 写在最前面,mysql事务是在Innodb引擎中得以实现的,如果这点不了解的话,请自行了解. 事务直接数据的可见性通过MVCC(多版本并发控制) ...

  4. Java 笔试题

    有一些还是存在问题,欢迎大家一起探讨. 在Java类中,使用以下( )声明语句来定义公有的int型常量MAX. A. public int MAX = 100; B. final int MAX = ...

  5. IP地址,子网掩码,网段表示法,默认网关,DNS服务器详解,DNS域名设计

    本文参考:<计算机网络: IP地址,子网掩码,网段表示法,默认网关,DNS服务器详解> IP地址 概述 计算机要实现网络通信,就必须要有一个用于快速定位的网络地址.IP地址就是计算机在网络 ...

  6. hyfhaha大事记——luogu

    成就墙 AK CSP-J 初赛 AK CSP-J 复赛 CSP- J 一等奖 CSP-S 一等奖 大事记 2017-09-20 13:54 注册洛谷账号 之后洛谷一直处于沉沦状态 2018 2018- ...

  7. 学习之学习--混沌大学商学院--第一课--HHR计划

    <学习之学习> 第一课:混沌初开 李善友 1,课程目标:建立个人的多元思维模型,帮助企业找到创新驱动的增长战略. 2,创新:第二曲线创新,创新理论之父熊彼特. 3,核心课:第二曲线,非连续 ...

  8. 后台框架 FastAdmin V1.0.0.20200228 发布,为疫情防控作贡献

    后台框架 FastAdmin V1.0.0.20200228 发布,为疫情防控作贡献 https://www.oschina.net/news/113694/fastadmin-1-20200228- ...

  9. LeetCode中等题(二)

    题目一: 给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?找出所有满足条件且不重复的三元组. 注意:答案中不可以包含重复 ...

  10. 华为平板暴力禁用wifi

    删除以下配置文件及动态链接库: /system/etc/wifi/* /system/etc/permission/*wifi* /system/lib/*wifi*