给定数组 求和等于固定值 算法_初识双指针算法

数列一般都是存储在数组中,数组的下标就是数列的位置。通常把表示数组元素下标的变量称为指针(这里是借用专业术语“指针”)。在求解区间问题时(当然不仅仅是区间问题),我们用两个变量分别表示数组区间的起点和终点,当列举所有可能区间时一般需要双重循环,外重循环表示左端点,内层循环表示右端点,所以是平方的时间效率,因为当左端点变化一次,右端点又要从新开始枚举。但是有一类特殊问题,根据问题特殊性没有必要穷举所有区间,左右端点都是不断增大(或者减小的),这样只需要单层循环就能实现,把具有这样一种特殊性质的问题称为双指针问题。下面举例说明:

例1、给定一个n个元素的正整数数列,求和不超过M、长度最大的区间,请输出该区间的长度。(bxoi000802)

思路:穷举法,一般方法是双重循环穷举所有可能区间,用(a,b)表示枚举的区间。但是本题的a和b 都有单调递增的趋势。先固定a不动,b增大,区间和逐步向M靠近,一旦区间和超过M,则b固定不动,增加a,直到区间和再次小于M为止。重复这个过程。

双指针法有两个基本特征:1、用两个指针描述问题,2、指针都是固定向一个方向运动,当第一个指针右移时第二个指针不需要回溯,时间效率O(n)。

    下面用具体事例来模拟解释:

给定正整数数列,A={1,2,2,3,2,1,3,1,3,4} M=6

用状态(a,b)穷举所有的区间,我们需要穷举表格中所有的状态,然后判断是否合法。

3ce68a5a3b299995fe7d97b25e54fc77.png

 根据双指针算法,只需要沿着箭头遍历红色标记的状态就可以。

在枚举第一行状态时,当状态枚举到(1,4)时就可以停止,后面的状态表示的区间和大于M,肯定不合法。所以向下枚举(2,4)。就是前面说的b不动,增加a,直到状态合法为止,得到状态(3,4),然后向右扩展……。

(2,4)左边的状态都不需要列举判断,因为即使他们合法,他们也不会比他们所在列的上方第一个合法状态更优,例如(2,3)肯定比他上面的状态(1,3)差。(6,7)肯定比它上方的(4,7)更差。

所以,双指针算法解决区间问题,是对暴力穷举的一种优化,减少了穷举状态。能够用双指针算法解决的问题,左右指针受问题限制的约束,都必须具有单调性:指针只朝一个方向运动。这道题因为受总和不超过M,并且是求区间最大长度约束,所以a,和b都是向右边交替移动。

设计代码时重点考虑以下几点:

1、左右指针的变化规律

       本例题中,如果当前区间和小于s,则右指针加1,相当于区间扩大一个元素,如果当前区间和大于或者等于s,则左指针加1,相当于区间减少一个元素,都是一直在向右运动。

2、指针在变化时,区间就在变化,描述区间的一些信息就在变化新的区间信息怎么由上一个区间递推出来(或者直接求解出来)

本例题中如果当前区间是通过改变右端点而来,则当前区间的和等于原来区间的和加上的右端点位置上的值;当前区间如果是通过改变左端点而来,则当前区间的和等于原来区间的和减去原来的左端点位置上的值。

3、自己模拟这个过程找到循环结束的条件

   本题当中,只有当前区间和小于s才会增加右端点,如果右端点增加到超过n时,则循环可以停止,因为左端点如果再移动只会使得区间和变小;只有当前区间和大于或者等于s时才会增加左端点,如果左端点等于右端点,此时区间长度为1,假如此时的区间和不小于s,则左端点还会继续增加(此时得到一个区间长度为1的合法区间),这时候左端点大于右端点,循环也可以停止,因为以后再也不会有比长度1更短的区间了。

伪代码如下:

 A=1,b=1,tot=data[1];//a、b表示区间,tot表示区间的和

 Ans=1000000;

For(;a<=b&&b<=n;)//注意此处的条件

{

   If(tot注意先b++

   Else

 {

   if(ans>b-a+1) ans=b-a+1;//得到新的合法方案,更新长度

tot-=data[a];

a++;//注意此处a++在最后

}

}

练习(bxoj000803):为了复习课本准备考试,J想用最少的时间看书,当然书很厚总共有P(1 ≤P≤ 1000000)页,每一页都包含一个知识点,但是有些知识点是重复的,每个知识点都有唯一一个整数表示(不超过10000)。J复习时总是选择某一页开始连续的看书复习。为了能复习全部,并且还能看的页数最少,问最少看几页?

2(000807)小阳买水果

给定一个长度为n(n<=2*10^6)的数列,每个元素的绝对值小于1000,求满足区间和为正的所有连续区间的最大长度。

解题思路:

求出前缀和数列,问题等价于求最大的{j-i},满足i0

i看做一个指针枚举左端点,j看作一个指针枚举右端点。

在枚举i指针指向元素时,前缀和数列的相邻两个元素,如果第一个小于第二个,那么第二个肯定没有必要列举,因为充当区间左端点第一个更优。所以可以把前缀和数列从第一个元素开始(第一个必须存在),预处理维护一个下降数列,作为区间左端点的候选元素。

同样的在枚举j指针指向的元素时,前缀和数列的相邻两个元素,如果第一个小于第二个,则第一个肯定没有必要枚举,充当区间右端点时第二个更优。所以可以把前缀和数列从最后一个元素开始倒着来(最后一个必须存在),预处理维护一个上升数列(从左往右看还是下降序列),作为区间右端点的候选元素。

举例说明:

设前缀和序列为:11 5 7 4 8 3 9 11 2 5 3

当5和7同时作为候选区间左端点时,因为5在左边肯定更优,所以从第一个元素开始维护一个下降序列:11 5 4 3 2.

当2和5同时作为候选区间右端点时,因为5在右边肯定更优,所以从最后一个元素开始,从右往左维护一个上升序列:11 5 3

所以i的取值为第一个下降训练,j的取值为第二个下降序列。

我们可以双重循环枚举二元组(i,j)的所有情况,但是因为个数列具有单调性,初始时i指向第一个数列的左边,j指向第二个数列的最左边。

11  5  4  3  2.     11  5  3

i                          j

因为j指向的元素11不满足要求,这时候i++,i指向下一个元素5。

11  5  4  3  2.      11  5  3

i                     j

J指向的元素大于i指向的元素,满足要求,这时候j++,指向下一个元素5

11  5  4  3  2.      11  5  3

i                           j

重复这个过程,发现i和j都是同向运动,符合双指针特点,所以用双指针解决优化一维。

请同学们结合例1当中总结的双指针三点注意事项(考虑左右指针变化规律;左右指针所代表的实际意义所对应的信息怎么递推求出来;循环结束的条件)自己尝试写出代码。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值