双指针简析(二分法|快慢指针|滑动窗口)

本文详细介绍了双指针技术在解决算法问题中的应用,包括二分法、快慢指针和滑动窗口。通过具体的LeetCode题目,如搜索插入位置、链表的中间节点和无重复字符的最长子串,展示了不同场景下双指针的使用策略。快慢指针常用于解决链表问题,滑动窗口则适用于寻找最优子序列。这些方法在处理序列数据时能有效降低时间复杂度,提高效率。
摘要由CSDN通过智能技术生成

双指针简析(二分法|快慢指针|滑动窗口)

一、简述

应用场景
​      双指针通常出现在从一个长序列中寻找目标子序列(子节点、或者某种特殊状态)的问题之中。对于此类问题如果采用双循环的方式,我们通常也能达成目标结果,但是需要开销的时间复杂度往往是 O ( n 2 ) O({n^2}) O(n2) , 在一些有运算时间限制的问题中是不能满足条件的, 而通过二指针的方法,可以将时间复杂度降低到 O ( n ) O(n) O(n)

原理

​​      双指针简单来说就是在一个长序列中设置两个指针,其中一个指针作为左边界(端点), 另一个作为右边界(端点)让它们共同包裹出一个区间。不断地去维护这两根指针(左右移动两根指针),使得在被包裹出的子区间中(子节点)能够在最大程度上满足我们所想要达成的某种特定条件。

二、双指针问题细化

​​      双指针通常有三种比较常见的形式,分别是:二分法,快慢指针, 滑动窗口。本着具体问题,具体分析的形式。对于以上的每种形式, 在此处都通过一道在leetcode上的题目来分析。

1. 普通双指针(二分法)


35. 搜索插入位置

题目描述

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。
如果目标值不存在于数组中,返回它将会被按顺序插入的位置。

​ 请必须使用时间复杂度为 O ( l o g n ) O({log n}) O(logn) 的算法。

解题步骤

​ 本道题题目实际上就是典型的二分法,具体步骤如下

  1. 设置左右指针分别为left 和 right, 分别指向数组的俩个端点。

  2. 获取中间指针: mid =(left + right)/ 2

  3. 判断 mid 所指向的数值,条件如下:

    (1) 如果 mid 所指向的数值大于目标值,那么 left 指针指向 mid + 1

    (2) 如果 mid 所指向的数值小于目标值,那么 right 指针指向 mid - 1

    (3) 如果 mid 所指向的数值恰好是目标值, 则返回 mid

  4. 重复 2,3步直到 left 指针 越过了 right 指针 (left > right), 表示目标不存在于数组之中。

tips: 本题考察的一点就是,如果搜索的目标不存在于数组之中, 那么该目标所要插入的位置, 恰恰是二分法寻找失败后 left 指针所指向的地址。

简单的实现代码如下(递归的方式)

var searchInsert = function(nums, target) {
  let binarySearch = (left, right) => {
      if(left > right)
          return left;
      let mid = parseInt((left + right) / 2);
      if(nums[mid] == target)
          return mid;
      return (nums[mid] > target) ? binarySearch(left, mid - 1) : binarySearch(mid + 1, right) 
  }
  return binarySearch(0, nums.length - 1)
};
2. 快慢指针

简单描述|应用场景

​​      快慢指针,一般被应用于寻找目标串中的某个特定位置如中间节点 或者 是解决链表环路问题之中。(快慢指针主要用于存储地址不连续的情况,即无法直接用下标检索目标数据的情况)

876. 链表的中间结点

题目描述

​ 给定一个头结点为 head 的非空单链表,返回链表的中间结点。

​ 如果有两个中间结点,则返回第二个中间结点。

解题步骤

1. 分别设置两个指针slow 和 fast
2. 每次让 fast 指针移动两个步长, slow 指针移动一个步长
3. 重复执行第二步,直到fast指针的下一个节点或者下下个节点为空
4. 返回 slow 指针

tips: 本题目中, 有一个雷点, 就是需要判断长序列是奇数还是偶数。

简单的实现如下

var middleNode = function(head) {
    let fast, slow;
    for(fast = head,slow = head; fast.next && fast.next.next; fast = fast.next.next, slow = slow.next);
    return (fast.next) ? slow.next:slow;
};
3 滑动窗口

简单描述|应用场景

​ 滑动窗口, 主要被应用于寻找最优目标子序列的情况下。

其思路如下:

		1. 设置两个左右指针, 分别指向长序列初始位置。
  		2.  不断移动右指针, 来扩大子序列所包裹的范围
		3.  当子序列所包裹的范围无法满足目标条件时, 调整右指针位置, 直到子序列恢复到满足目标条件的状态
		4.  重复执行2,3步骤, 直到右指针移动到长序列的中止位置。

3. 无重复字符的最长子串

题目描述

​ 给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。

解题步骤

  1. 设置左右指针, 使它们同时指向第一个元素, 将首元素存进set之中。

  2. 移动右指针, 如果 set中存在右指针所指向的元素, 执行第 3 步, 否则执行第 4 步。

  3. 移动左指针, 并在 set中删除 左指针所指向的元素, 直到 set 中不存在右指针所指向的元素,然后将右指针所指向的元素

    4. 移动右指针, 将右指针所指向的元素添加进 set 容器之中。
    
    1. 重复 2 — 4 步骤, 直到遍历完一遍整个数组。

执行流程

​ ​      原数组: d => a => c => a => b => c => d
​ ​      右指针:#
​ ​      左指针:#
​ ​      第一次执行到 步骤3 时的状态:
​ ​      原数组: d => a => c => a => b => c => d
​ ​      右指针:# => #=> # => #
​ ​      左指针:#
​ ​      被左右指针包裹的子数组: d, a, c, a
​ ​      执行 步骤3:
​ ​      原数组: d => a => c => a => b => c => d
​ ​      右指针:# => # => # => #
​ ​      左指针:# => # => #
​ ​      被左右指针包裹的子数组:c, a

简单的实现如下:

var lengthOfLongestSubstring = function(s) {
    if(!s||s == "")
        return 0;
    if(s.length == 1)
        return 1;
    let left = 0,
    right = 0,
    set = new Set(), count = 0, max = 0;
    while(s[right]) {
        if(set.has(s[right])) {
            // 移动左指针
            while(set.has(s[right])) {
                set.delete(s[left++])
                count--;
            }
            // 将右指针当前元素并入set
            set.add(s[right])
            count++;
        }
        else {
            set.add(s[right])
            count++;
        }
        if(count > max)
            max = count;
        right++;
    }
    return max;
};
三、其他二指针问题|题目解析
四 、总结

​ 综合上述分析, 我们可以发现, 快慢指针 和 滑动窗口 和 普通二指针方法是略有区别的, 其区别在于普通二指针方法对于左右指针的移动比较随意, 而 快慢指针 和 滑动指针在对左右指针的移动上有着一定的约束。 普通的二指针方法(特指二分法)往往不需要遍历整个长串,复杂度较低, 而后续两种方法往往都要完成一次完整的遍历。

​ 当然,它们都非常适合被应用于在一个序列中匹配最优子序列的问题之中!

题目来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/search-insert-position
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值