zoukankan      html  css  js  c++  java
  • 尺取法(双指针)--算法竞赛

    尺取法

    尺取法(又称为:双指针、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

  • 相关阅读:
    Ubuntu14.04安装一个小问题,搜狗输入法
    HDU 2612 -Find a way (注重细节BFS)
    cmd介面,进adb命令提示符error
    软件开发V型号
    采用tcpdump攫Android网络数据包
    Effective C++ 18-23
    ExtJS得知--------Ext.Element学习的查询方法(示例)
    基OOP知识
    Openfire开发配置,Openfire源码配置,OpenFire二次开发配置
    真正菜鸟用教程之WQSG Scrip Export WQSG (脚本导出导入工具,PSP、NDS汉化必备 )
  • 原文地址:https://www.cnblogs.com/BlairGrowing/p/14070566.html
Copyright © 2011-2022 走看看