当前位置: 首页 > news >正文

深入理解【双指针】:从基础概念到实际例题

双指针篇

在 Java 中,双指针(Two Pointers)是一种常见的算法技巧,通常用于解决数组、链表或字符串相关的问题。它通过使用两个指针(索引或引用)来遍历数据结构,以减少时间复杂度或空间复杂度。根据问题的类型和指针的移动方式,双指针可以分为以下几种分类。


双指针类型讲解

1. 对撞指针(Two Pointers Moving Towards Each Other)

  • 描述: 两个指针分别从数组的两端向中间移动,直到满足某个条件或相遇。
  • 适用场景: 常用于有序数组的查找问题(如两数之和)、反转问题或分区问题。
  • 时间复杂度: 通常为 O(n)。
  • 例子: 两数之和(Two Sum,假设数组已排序)。
示例代码
public class TwoSum {
    public static int[] twoSum(int[] nums, int target) {
        int left = 0;
        int right = nums.length - 1;
        
        while (left < right) {
            int sum = nums[left] + nums[right];
            if (sum == target) {
                return new int[]{left, right};
            } else if (sum < target) {
                left++;
            } else {
                right--;
            }
        }
        return new int[]{-1, -1}; // 未找到
    }

    public static void main(String[] args) {
        int[] nums = {2, 7, 11, 15};
        int target = 9;
        int[] result = twoSum(nums, target);
        System.out.println("[" + result[0] + ", " + result[1] + "]"); // 输出 [0, 1]
    }
}

2. 快慢指针(Fast and Slow Pointers)

  • 描述: 一个指针(快指针)移动速度快,另一个指针(慢指针)移动速度慢,通常快指针每次走两步,慢指针每次走一步。
  • 适用场景: 常用于链表问题(如检测环、找中间节点)或数组中的循环检测。
  • 时间复杂度: 通常为 O(n)。
  • 例子: 检测链表中是否有环(LeetCode 141)。
示例代码
class ListNode {
    int val;
    ListNode next;
    ListNode(int val) {
        this.val = val;
    }
}

public class LinkedListCycle {
    public boolean hasCycle(ListNode head) {
        if (head == null || head.next == null) return false;
        
        ListNode slow = head;
        ListNode fast = head;
        
        while (fast != null && fast.next != null) {
            slow = slow.next;         // 慢指针走一步
            fast = fast.next.next;    // 快指针走两步
            if (slow == fast) {       // 相遇说明有环
                return true;
            }
        }
        return false;
    }
}

3. 同向指针(Two Pointers Moving in the Same Direction)

  • 描述: 两个指针从同一端开始,向同一方向移动,通常一个指针用于遍历,另一个指针用于标记或记录。
  • 适用场景: 常用于滑动窗口问题、子数组问题或字符串匹配。
  • 时间复杂度: 通常为 O(n)。
  • 例子: 找到最小的子数组和(LeetCode 209)。
示例代码
public class MinSubArrayLen {
    public static int minSubArrayLen(int target, int[] nums) {
        int left = 0;
        int sum = 0;
        int minLen = Integer.MAX_VALUE;
        
        for (int right = 0; right < nums.length; right++) {
            sum += nums[right];
            while (sum >= target) {
                minLen = Math.min(minLen, right - left + 1);
                sum -= nums[left];
                left++;
            }
        }
        return minLen == Integer.MAX_VALUE ? 0 : minLen;
    }

    public static void main(String[] args) {
        int[] nums = {2, 3, 1, 2, 4, 3};
        int target = 7;
        System.out.println(minSubArrayLen(target, nums)); // 输出 2
    }
}

4. 固定距离指针(Fixed Distance Pointers)

  • 描述: 两个指针之间的距离固定,通过移动这两个指针来解决问题。
  • 适用场景: 常用于字符串匹配、定长子数组问题。
  • 时间复杂度: 通常为 O(n)。
  • 例子: 检查字符串中是否有重复字符的最长子串(固定窗口大小)。
示例代码
public class CheckDuplicates {
    public static boolean checkDuplicates(String s, int k) {
        int[] count = new int[26];
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            count[c - 'a']++;
            if (i >= k) {
                count[s.charAt(i - k) - 'a']--;
            }
            if (count[c - 'a'] > 1) {
                return true; // 发现重复字符
            }
        }
        return false;
    }

    public static void main(String[] args) {
        String s = "abcba";
        int k = 3;
        System.out.println(checkDuplicates(s, k)); // 输出 true
    }
}

总结

类型指针移动方式典型问题
对撞指针从两端向中间移动两数之和、反转数组
快慢指针一个快一个慢链表环检测、中间节点
同向指针同方向不同速滑动窗口、子数组问题
固定距离指针保持固定间距移动定长子串、重复字符检测

双指针的核心在于通过巧妙地移动指针来避免嵌套循环,从而优化算法效率。

二、例题演示

1. 美丽的区间

题目描述

给定一个长度为 nn 的序列 a1,a2,⋯,ana1,a2,⋯,a**n 和一个常数 SS

对于一个连续区间如果它的区间和大于或等于 SS,则称它为美丽的区间。

对于一个美丽的区间,如果其区间长度越短,它就越美丽。

请你从序列中找出最美丽的区间。

输入描述

第一行包含两个整数 n,Sn,S,其含义如题所述。

接下来一行包含 nn 个整数,分别表示 a1,a2,⋯,ana1,a2,⋯,a**n

10≤N≤10510≤N≤105,1×ai≤1041×a**i≤104,1≤S≤1081≤S≤108。

输出描述

输出共一行,包含一个整数,表示最美丽的区间的长度。

若不存在任何美丽的区间,则输出 00。

输入输出样例

示例 1

输入

5 6
1 2 3 4 5

输出

2

解题思路:

  • 本题双指针的类型属于快慢指针,通过low、high两个指针,实现检索最小数对,通过两个while循环,减少了算法复杂度。
  • 本题不可以用排序,排序会导致数组顺序变化,因为前后数字关系他们的和。笔者也是一开始混进了排序的思路漩涡,倒是卡住了。

import java.util.Scanner;
// 1:无需package
// 2: 类名必须Main, 不可修改

public class 蓝桥杯真题_美丽的区间 {
    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        int n = scan.nextInt();
        int s = scan.nextInt();
        int[] a = new int[n];
        for (int i=0;i<n;i++) {
            a[i] = scan.nextInt();
        }
        int low = 0;
        int high = 0;
        int res = n;
        int sum = 0;
      while (high < n) {
          sum = sum + a[high];
            while(sum>=s) {
                res = Math.min(res,high-low+1);
                sum = sum -a[low];
                low++;
            }
            high++;
        }
      if(res == n) {
          System.out.println(0);
      }
      else {
          System.out.println(res);
      }
        scan.close();
    }
}

2.聪明的小羊肖恩

问题描述

小羊肖恩是一只非常聪明的绵羊,在牧场里与其他绵羊一起生活。有一天,它在草地上漫步时,发现了一些数字。它猜想这些数字可能在某些方面有用,于是把它们带回了牧场,并开始研究它们。

具体来说,小羊有一个长度为 nn 的数组,第 ii 个数字的值为 aia**i。小羊肖恩心中想了两个数 LL 和 RR,它想知道有多少对下标对 (i,j)(i,j) 满足以下条件:

  • 1≤i<j≤n1≤i<jn
  • L≤ai+aj≤RLa**i+a**jR

请你帮它找出满足条件的下标对数量。

输入格式

第一行输入三个整数 nn,LL 和 RR

第二行输入 nn 个整数 a1,a2,a3,…,ana1,a2,a3,…,a**n,表示数组 aa

数据范围保证:1≤n≤2×1051≤n≤2×105,1≤ai≤1091≤a**i≤109,1≤L≤R≤1091≤LR≤109。

输出格式

输出一个整数,表示满足条件的下标对数量。

样例输入

3 2 4
1 2 3

样例输出

2

说明

样例中满足条件的下标对有 (1,2)(1,2) 和 (1,3)(1,3)。

思路解答
  • 本题类型即是相撞指针类型,通过找到两个点的区间,求出答案。
import java.util.Scanner;
import java.util.Arrays;
// 1:无需package
// 2: 类名必须Main, 不可修改

public class 蓝桥杯真题_聪明的小羊肖恩 {
    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        long n = scan.nextLong();
        long l = scan.nextLong();
        long r = scan.nextLong();
        long res = 0;
        long[] a = new long[(int)n];
        for (long i=0;i<n;i++) {
            a[(int)i] = scan.nextInt();
        }
        Arrays.sort(a);//先排序,完成数组的单调性

        for(long i=0;i<n;i++) {
            long j = n-1; //j做右指针
            while(j>i&&a[(int)j]+a[(int)i]>r) {
                j--;//先找一边
            }
            long k = i+1;
            while (k<=j&&a[(int)i]+a[(int)k]<l) {
                k=k+1;
            }
            res = res +Math.max(0,j-k+1);
        }

        System.out.print(res);
        scan.close();
    }
}
3.连续子序列的个数

问题描述

给你一个长度为 nn 的数组 aa 和一个数字 mm ,请你计算这个数组有多少个连续子序列的和大于等于 mm

如果两个连续子序列来自数组中的不同位置,我们认为它们是不同的,即使它们在内容上是相同的。

输入格式

第一行输入两个整数表示 nn 和 mm

第二行输出 nn 个整数表示 aa 数组的元素。

输出格式

输出一个整数表示 aa 数组有多少个连续子序列的和大于等于 mm

样例输入

4 10
6 1 2 7

样例输出

2
思路解答
  • 本题类型即是滑动窗口类型,通过找到两个点的区间,求出答案。
import java.util.Scanner;

public class 蓝桥杯真题_连续子序列的个数 {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        // 读取 n 和 m 的值
        long n = scanner.nextLong();
        long m = scanner.nextLong();

        // 创建长度为 n 的数组来存储输入的元素
        long[] a = new long[(int) n];
        for (int i = 0; i < n; i++) {
            a[i] = scanner.nextLong();
        }

        // 初始化左右指针、当前子数组的和以及满足条件的子数组数量
        long left = 0;
        long right = 0;
        long sum = 0;
        long cnt = 0;

        // 使用滑动窗口来遍历数组
        while (right < n) {
            // 累加当前元素到总和
            sum += a[(int) right];
            // 当总和大于等于 m 时,更新满足条件的子数组数量并调整左指针
            while (sum >= m) {
                cnt += (n - right);
                sum -= a[(int) left];
                left++;
            }
            // 右指针右移
            right++;
        }

        // 输出满足条件的子数组数量
        System.out.println(cnt);
        scanner.close();
    }
}

双指针的核心在于通过巧妙地移动指针来避免嵌套循环,从而优化算法效率。希望这些分类和例子能帮到你!😁

相关文章:

  • 【实测】单卡跑满血版DeepSeek|CSGHub集成KTransformers
  • 算法——广度优先搜索——跨步迷宫
  • Spark大数据分析与实战笔记(第四章 Spark SQL结构化数据文件处理-01)
  • Java 并发集合:ConcurrentHashMap 深入解析
  • 「C++输入输出」笔记
  • 上取整,下取整,四舍五入
  • IC/ID卡的卡号的不同格式的转换
  • created在vue3 script setup中的写法
  • redis搭建一主一从+keepalived(虚拟IP)实现高可用
  • 【8】分块学习笔记
  • 修改War包文件
  • PTA C语言程序设计 第三章
  • linux Redhat9.5采用DNS主从实现跨网段解析
  • 批量删除 PPT 中的所有图片、某张指定图片或者所有二维码图片
  • 【Java】——方法的使用(从入门到进阶)
  • deepseek使用记录99——为何追问
  • nginx 配置ip黑白名单
  • Docker 内部通信(网络)
  • 基于MySQL的创建<resultMap>和查询条件<if test>
  • zookeepernacoskafka之间的联系
  • 前四月全国铁路完成固定资产投资1947亿元,同比增长5.3%
  • 巴总理召开国家指挥当局紧急会议
  • 心相印回应官方旗舰店客服辱骂消费者:正排查
  • 治沙“异瞳”男生疑似摆拍,团队称合作12天多期视频为策划拍摄
  • 两部门部署中小学幼儿园教师招聘工作:吸纳更多高校毕业生从教
  • 安徽六安原市长潘东旭,已任省市场监督管理局党组书记、局长