尺取法(又称为双指针、Two Pointers)是算法竞赛中一个常)用的优化技巧,用来解决序列的区间问题,操作简单,容易编程。如果区间是单调的,也常常用二分法求解,所以很多问题用尺取法和二分法都行。另外,尺取法的操作过程和分治算法的步骤很相似,有时也用在分治中。
概念
什么是尺取法?为什么尺取法能用来优化?考虑下面的应用背景:
(1)给定一个序列,有时需要它是有序的,先排序;
(2)问题和序列的区间有关,且需要操作两个变量,可以用两个下标(指针)i和j扫描区间。
对于上面的应用,一般的做法是用i和j分别扫描区间,有二重循环,复杂度为O(n2)。
以反向扫描(即i与j的方向相反,后文有解释)为例,代码如下:
for(int i = 0; i<n; i++) //i从头扫到尾
for(intj=n-1; n-1; j>= 0; j-- ){
//j从尾扫到头
...
}
下面用尺取法优化上述算法。
实际上,尺取法就是把二重循环变为一个循环,在这个循环不中同时处理i和j。复杂度也就从O(n2)变为O(n)。仍以上面的反向扫描为例,代码如下:
//用while实现
int i = 0, j = n - 1;
while (i<j){
//i和j在中间相遇,这样做还能防止i和j越界
i++;//i从头扫到尾
j--;//j从尾扫到头
}
//用for实现
for(inti= 0, j=n-1;i<j;i++,j--){
//满足题意的操作
}
在尺取法中,i和j有以下两种扫描方向:
(1)反向扫描。i和j方向相反,让i从头到尾,j从尾到头,在中间相会。
(2)同向扫描。i和j方向相同,都从头到尾,速度不同,如让j跑在i前面。
把同向扫描的i、j指针称为"快慢指针",把反向扫描的i、j指针称为"左右指针",更加形象。其中,“快慢指针"在序列上产生了一个大小可变的"滑动窗口”,有灵活的应用,如寻找区间、数组去重、多指针问题。
反向扫描
用下面的几个应用说明反向扫描的编码方法。
一、找指定和的整数对
这个问题是尺取法中最经典,也最简单直接的应用。

为了说明尺取法的优势,下面给出4种解题方法。
(1)用二重循环暴力搜索,枚举所有的取数方法,复杂度为0(n2),超时。暴力法不需要排序。
(2)二分法。首先对数组从小到大排序,复杂度为O(nlog2n);然后从头到尾处理数组中的每个元素a[i],在大于a[i]的数中二分查找是否存在一个等于m-a[i]的数,复杂度也为O(nlog2n)。两部分相加,总复杂度仍然为O(nlog2n)。
(3)哈希。分配一个哈希空间s,把n个数放进去。逐个检查a[]中的几个数,如a[i],检查m-a[i]在s中是否有值,如果有,那么存在一个答案。复杂度为O(n)。哈希方法很快,但是需要一个额外的很大的哈希空间。
(4)尺取法。这是标准解法。首先对数组从小到大排序;然后设置两个变量i和j,分别指向头和尾,i初值为0,j初值为n-1,然后让i和j逐渐向中间]移动,检查a[i]+a[j],如果大于m,就让j减1;如果小于m,就让i加1,直至a[i]+a[j]=m。排序复杂度为O(nlog2n),检查的复杂度为O(n),总复杂度为O(nlog2n)。
尺取法代码如下,注意可能有多个答案。
// 函数用于找到数组中两数之和等于给定值 m 的两个数
void find_sum(

908

被折叠的 条评论
为什么被折叠?



