一起学算法(双指针篇)

文章介绍了如何使用尺取法(双指针算法)来解决寻找字符串中不含重复字符的最长子串问题。通过初始化两个指针,不断调整区间并检查是否有重复字符,更新最长子串的长度。此外,文中还提到了算法的适用条件,如单调性和时效性,并给出了其他可以应用双指针算法的LeetCode题目示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

概念:

        通过两个指针,不断的调整区间,从而求出问题最优解的算法就叫“尺取法”,由于利用的是两个双指针,所以也叫作“双指针”算法,这里的“尺”的含义,主要是因为这类问题,最终要求解的都是连续的序列(子串),就好比一把尺子,故而得名

1.最长不重复子串

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

 1.初步分析

  • n<=10^7;
  • 最长
  • 所有的字符不重复
  • 子串

       根据上面的几个关键词,我们可以得出一些结论,首先,根据n的范围已经能大致确认这是一个需要O(n)或者O(nlongn)的算法才能解决这个问题;其次,“最长”这个词告诉我们,可能是一个动态规划问题或者是贪心问题,判断字符是否重复可以用hash表在O(1)的时间内进行判断,最后枚举所有的子串是O(n^2)的

2.朴素算法

用max记录我们所需要的最大不重复子串的长度,用一个hash表代表某个字符是否出现过,算法的描述如下:

1.枚举子串的右端点i=0~n-1;

2.当hash表中出现hash[i]>1的时候,相当于双指针指向的区域范围内出现了重复字符,然后移动左指针,直到移出重复字符的时候,对指针区域长度求值,不断的移动,直到找到最大的无重复子串

     public int lengthOfLongestSubstring(String s) {
        if (s == null || s.length() == 0) {
            return 0;
        }
        int left=0;
        int max=0;
        //将字符串转换为char
        char[] str=s.toCharArray();
        int[] hash=new int[128];
        for(int right=0;right<s.length();right++){
            char c=str[right];
            hash[c]++;
            while(hash[c]>1){
                hash[str[left++]]--;
            }
            int val=right-left+1;
            max=Math.max(val,max);
        }
        return max;
    } 

       下面附一张不断枚举左端点的动态图,方便大家理解记忆(如果枚举左端点的话,最后还有额外的去判断是否越界,枚举右端点则不需要)

       当区间内[i,j]中存在重复(红色)字符时,左指针i自增;否则右指针j自增,这部分大家可以自行下去尝试

2.算法描述

算法描述如下:

1.初始化i=0   j=i-1  代表一开始“尺子”的长度为0;

2.增长“尺子”的长度,即j=i+1;

3.判断当前这把“尺子”[i,j]是否满足题目给出的条件

   1.如果满足,记录最优解

   2.如果不满足,则减少“尺子”长度,缩小左端点 i=i+1 再次判断是否符合条件

满足条件时,j++,不满足条件时,i++;

 双指针满足的前提条件:

  • 单调性

      举个栗子:[2 6 9 5 12 5] 这种数组可以用双指正解决么?每次移动它的情况是不确定的

任意一个指指针的增加,条件满足与否只会出现两种情况,即:

满足—>不满足或者是不满足—>满足不会出现 满足—>不满足—>满足

  • 时效性

       必须要在O(1)或者O(log2n)的时间内,求出当前区间[i,j]是否满足既定条件,否则无法用这种算法进行求解

leetcode题单:

反转字符串

    public void reverseString(char[] s) {
        int i=0;
        int j=s.length-1;
        while(i<j){
            char temp=s[i];
            s[i]=s[j];
            s[j]=temp;
            i++;
            j--;
        }
    }

判断子序列

    public boolean isSubsequence(String s, String t) {
      if(s==null||t==null){
          return false;
      }
      int i=0;
      int j=0;
      while(i<s.length()&&j<t.length()){
            if(s.charAt(i)==t.charAt(j)){
                i++;
                j++;
            }else{
                j++;
            }
      }
      return i==s.length();
    }

两数之和

       public int[] twoSum(int[] nums, int target) {
        int[] num = new int[2];
        if (nums == null || nums.length == 0) return num;
        int left=0;
        int right=nums.length-1;
        Arrays.sort(nums);
         while(left<right){
             if(nums[right]+nums[left]==target){
                num[0]=left;
                num[1]=right;
                return num;
            }else if(nums[right]+nums[left]<target){
                  left++;
            }else{
                right--;
            }
            }
        return num;
    }

无重复字符的最长子数组

     public int lengthOfLongestSubstring(String s) {
        if (s == null || s.length() == 0) {
            return 0;
        }
        int left=0;
        int max=0;
        //将字符串转换为char
        char[] str=s.toCharArray();
        int[] hash=new int[128];
        for(int right=0;right<s.length();right++){
            char c=str[right];
            hash[c]++;
            while(hash[c]>1){
                hash[str[left++]]--;
            }
            int val=right-left+1;
            max=Math.max(val,max);
        }
        return max;
    } 

统计公平对的数目

public long countFairPairs(int[] nums, int lower, int upper) {
    //同向双指针
    Arrays.sort(nums);
    long count = 0;
    for (int i = 0; i < nums.length; i++) {
        int num = nums[i];
        //upper-num+1意思就是大于这个目标值的最左侧,右端是不包含
        int m = nearIndex(nums, 0, i, upper - num + 1);
        //lower-num意思就是大于lower-num的最左侧,左端是包含的
        int n = nearIndex(nums, 0, i, lower - num);
        count += m - n;//左闭右开
    }
    return count;
}

//二分搜索最左侧
public int nearIndex(int[] nums, int left, int right, int target) {
    if (left >= right) {
        return left;
    }
    int i = left;
    int j = right;
    while (i < j) {
        int mid = i + (j - i) / 2;
        if (nums[mid] >= target) {
            j = mid;
        } else {
            i = mid + 1;
        }
    }
    return i;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

吃橘子的Crow

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值