尺取法(双指针)--算法竞赛

尺取法

尺取法(又称为:双指针、two pointers),是算法竞赛中一个常用的优化技巧,用来解决序列的区间问题,操作简单、容易编程。
如果区间是单调的,也常常用二分法来求解,所以很多问题用尺取法和二分法都行。

尺取法的概念

什么是尺取法?为什么尺取法能优化呢?
考虑下面的应用背景:
 (1)给定一个序列。有时候需要它是有序的,先排序。
 (2)问题和序列的区间有关,且需要操作2个变量,可以用两个下标(指针)i、j扫描区间。
对于上面的应用,一般的做法,是用i、j分别扫描区间,有两重循环,复杂度O(n2)。以反向扫描(即i、j方向相反,后文有解释)为例,

代码是:

for(int i = 0; i < n; i++)           //i从头扫到尾
    for(int j = n-1; j >= 0; j--){   //j从尾扫到头
        ......
        }

优化:

//用while实现:
int i = 0, j = n - 1;
while (i < j) {      //i和j在中间相遇。这样做还能防止i、j越界
        ......       //满足题意的操作
        i++;         //i从头扫到尾
        j--;         //j从尾扫到头
}
//用for实现:
for (int i = 0, j = n - 1; i < j; i++, j--) {
    ......
}

把同向扫描的i、j指针称为“快慢指针”

把反向扫描的i、j指针称为“左右指针”

一、快慢指针的常见算法

1、判定链表中是否含有环

经典解法就是用两个指针,一个跑得快,一个跑得慢。如果不含有环,跑得快的那个指针最终会遇到 null,说明链表不含环;

如果含有环,快指针最终会超慢指针一圈,和慢指针相遇,说明链表含有环。

boolean hasCycle(ListNode head) {
    ListNode fast, slow;
    fast = slow = head;
    while (fast != null && fast.next != null) {
        fast = fast.next.next;
        slow = slow.next;
        
        if (fast == slow) return true;
    }
    return false;
}

2、已知链表中含有环,返回这个环的起始位置

ListNode detectCycle(ListNode head) {
    ListNode fast, slow;
    fast = slow = head;
    while (fast != null && fast.next != null) {
        fast = fast.next.next;
        slow = slow.next;
        if (fast == slow) break;
    }
    // 上面的代码类似 hasCycle 函数
    slow = head;
    while (slow != fast) {
        fast = fast.next;
        slow = slow.next;
    }
    return slow;
}

可以看到,当快慢指针相遇时,让其中任一个指针指向头节点,然后让它俩以相同速度前进,再次相遇时所在的节点位置就是环开始的位置。

3、寻找链表的中点

类似上面的思路,我们还可以让快指针一次前进两步,慢指针一次前进一步,当快指针到达链表尽头时,慢指针就处于链表的中间位置。

while (fast != null && fast.next != null) {
    fast = fast.next.next;
    slow = slow.next;
}
// slow 就在中间位置
return slow;

4、寻找链表的倒数第 k 个元素

我们的思路还是使用快慢指针,让快指针先走 k 步,然后快慢指针开始同速前进。这样当快指针走到链表末尾 null 时,

慢指针所在的位置就是倒数第 k 个链表节点(为了简化,假设 k 不会超过链表长度):

ListNode slow, fast;
slow = fast = head;
while (k-- > 0) 
    fast = fast.next;

while (fast != null) {
    slow = slow.next;
    fast = fast.next;
}
return slow;

二、左右指针的常用算法

左右指针在数组中实际是指两个索引值,一般初始化为 left = 0,right = nums.length - 1。

1、二分查找

前文 二分查找 有详细讲解,这里只写最简单的二分算法,旨在突出它的双指针特性:

int binarySearch(int[] nums, int target) {
    int left = 0; 
    int right = nums.length - 1;
    while(left <= right) {
        int mid = (right + left) / 2;
        if(nums[mid] == target)
            return mid; 
        else if (nums[mid] < target)
            left = mid + 1; 
        else if (nums[mid] > target)
            right = mid - 1;
    }
    return -1;
}

2、两数之和

∎问题描述
   输入n ( n≤100,000)个整数,放在数组a[]中。找出其中的两个数,它们之和等于整数m(假定肯定有解)。题中所有整数都是int型。
  样例输入:
  21 4 5 6 13 65 32 9 23
  28
  样例输出:
  5 23
  说明:样例输入的第一行是数组a[],第2行是m = 28。样例输出5和23,相加得28。

void find_sum(int a[], int n, int m){ 
     sort(a, a + n);      //先排序,复杂度O(nlogn)
     int i = 0, j = n - 1;    //i指向头,j指向尾
     while (i < j){           //复杂度O(n)
            int sum = a[i] + a[j];
            if (sum > m)   j--;
            if (sum < m)   i++;
            if (sum == m){     
                cout << a[i] << " " << a[j] << endl;  //打印一种情况
                i++;          //可能有多个答案,继续
            }
      }
}

 

3、反转数组

void reverse(int[] nums) {
    int left = 0;
    int right = nums.length - 1;
    while (left < right) {
        // swap(nums[left], nums[right])
        int temp = nums[left];
        nums[left] = nums[right];
        nums[right] = temp;
        left++; right--;
    }
}

4、判断回文串

给一个字符串,判断它是不是回文串。

#include <bits/stdc++.h>
using namespace std;
int main(){
    int n;
    cin >> n;                         //n是测试用例个数
    while(n--){
        string s;  cin >> s;          //读一个字符串
        bool ans = true;
        int i = 0, j = s.size() - 1;  //双指针
        while(i < j){ 
            if(s[i] != s[j]){
                ans = false;
                break;
            }
            i++;   j--;               //移动双指针
        }
        if(ans)   cout << "yes" << endl;
        else      cout << "no"  << endl;
    }
    return 0;
}

 

因上求缘,果上努力~~~~ 作者:每天卷学习,转载请注明原文链接:https://www.cnblogs.com/BlairGrowing/p/14070566.html

原文地址:https://www.cnblogs.com/BlairGrowing/p/14070566.html