LeetCode之一起攻克双指针与滑动窗口


本文总结典型的双指针题目与滑动窗口题目,以便读者和博主准备面试用。

双指针

1.两数之和

题目链接
在这里插入图片描述

思路

思路很简单,首先让left指向0,right指向数组的最后一位,我们比较numbers[left]+numbers[right]与target,如果二者之和大于target,说明right指向的值过大,要让right指针左移;如果二者之和小于target则说明left指向的值过小,要让left指针右移,相等则返回left与right。

代码

class Solution {
    public int[] twoSum(int[] numbers, int target) {
        int left = 0;
        int right = numbers.length-1;
        int[]sz = new int[2];
        while(left<right)
        {
            if(numbers[left]+numbers[right]>target)
                right--;
            else if(numbers[left]+numbers[right]<target)
                left++;
            else
                break;
        }
        sz[0] = left+1;
        sz[1] = right+1;
        return sz;
    }
}

2.平方数之和

题目链接
在这里插入图片描述

思路

与上一道题很接近,上道题的right是数组的最后一个,那么本题的right应该是什么呢,你可以直接设为c,但是最好是设置为根号c,因为a和b都是整数,最小也就是0,那么另一个数最大不会超过根号c,同时我们的left设置为1。
比较的过程与上一道题相似,这里不再赘述。

代码

class Solution {
    public boolean judgeSquareSum(int c) {
        //left可以等于0
        int left = 0;
        int right = (int)Math.sqrt(c);
        
        while(left<right)
        {
            int now = left*left+right*right;
            if(now>c)
                right--;
            else if(now<c)
                left++;
            else break;
        }
        return left*left+right*right==c;
    }
}

3.反转字符串中的元音字母

题目链接
在这里插入图片描述

思路

思路其实很简单哦,left指针指向0,right指针指向字符串的最后一位。当left遇到一个元音字母,那么就让right向前走直到也找到一个元音字母,让这两个元音字母交换,这里注意交换后,left和right两个指针也要去挪动。最后我们使用一个HashSet来存储这些元音,每次去判断当前字符是否是元音。
那么问题来了字符串中只有一个元音怎么办,其实这个问题也是在说如何终止循环,和前两道题相同,当left=right时就结束循环。

代码

class Solution {
    public String reverseVowels(String s) {
        //aeiou
      int left = 0;
      int right = s.length()-1;
      char[]ss = s.toCharArray();
      HashSet<Character>set = new HashSet<>();
      set.add('a');
      set.add('e');
      set.add('i');
      set.add('o');
      set.add('u');
      set.add('A');
      set.add('E');
       set.add('I');
      set.add('O');
      set.add('U');
      while(left<right)
      {
          char now = ss[left];
          if(!set.contains(now))
            {
                left++;
                continue;
            }
            else{
                while(right>left&&!set.contains(ss[right]))
                    right--;
                if(right>left)
                {
                   char r = ss[right];
                   ss[right] = now;
                    ss[left] = r;
                    //注意左右指针交换成功后要挪动 
                    left++;
                    right--;
                }

            }
      } 
      return new String(ss); 
    }
}

4.验证回文字符串2

在这里插入图片描述

思路

首先如何判断回文字符串呢,我们可以使用两个指针分别指向字符串的头和尾,然后判断指针指向的字符是否相等,如果不想等就不是回文字符串,如果相等,头指针向后移动,尾指针向左移动。
本题和普通验证回文字符串的区别是可以删除一个字符,那么如何去求解呢,这里借用一张图,首先正常判断回文字符串,当我们left指向b,right指向f时,可以删除b或删除f,那么我们就去判断cdf(删除了b),或者bcb(删除了f)是不是回文字符串,只要有一个是回文字符串就说明原始字符串可以通过删除一个字符,来变成回文字符串。
在这里插入图片描述

代码

我们写一个函数专门用来判断字符串是否为回文字符串。这里由于只能删除一个字符,所以当我们在validPalindrome函数中发现left指针与right指指向的字符不同,就直接返回删除左字符串或删除右字符串的结果。

class Solution {
    public boolean validPalindrome(String s) {
      int left = 0;
      int right = s.length()-1;
      while(left<right)
      {
          //可以去掉left所在的字符或去掉right所在的字符
          if(s.charAt(left)!=s.charAt(right))
            return isPalindrome(s,left+1,right)||isPalindrome(s,left,right-1);
            left++;
            right--; 
      }
      return true;
    }
    public boolean isPalindrome(String s,int i,int j){
        //当s为奇数长度时,i和j最终会相等
        //当s为偶数长度时,i最终会大于j
        while(i<j)
        {
            if(s.charAt(i)!=s.charAt(j))
                return false;
            i++;
            j--;
        }
        return true;
    }
}

5. 合并两个有序数组

题目链接
在这里插入图片描述

思路

我们可以用比较愚蠢的方法去做。
比如创建一个新的数组num3,然后依次比较nums1和nums2的数值,当nums1的值比nums2的值小,那么我们num3就选取nums1的值,然后指向nums1的指针就向后走一位,指向nums2的指针不动。一直比较下去直至一个指针指向了数组的最后,然后再把另一个数组中的所有数(如果有的话)都放入num3。
那么我们是否可以不使用额外空间呢?
我们可以使用nums1呀,他的空间既然足够大,那就是想用nums1来保存nums1和nums2的元素。因此我们从最后开始比较,和上面方法不同的是,我们先选取两个数组的最大值去比较,然后向前寻找第二大,第三大…,直至两个指针中有一个指针指向了0。该方法还有个什么好处呢,当我们判断结束后只需要判断指向nums2的指针是否为0,当nums2的指针不为0说明,nums2中剩余的元素是最小的几个数,我们要把他们放入nums1中,然而当指向nums1的指针不为0,说明nums1中剩余的元素是最小的几个数,可他们已经在nums1中了,所以不需要单独判断。
最后再说一下,本题指向nums1与指向nums2的指针就是m与n。同时要创建一个新的指针指向nums1的最后一位。

代码

class Solution {
    public void merge(int[] nums1, int m, int[] nums2, int n) {
        //归并排序(倒着归并)
        int i=0;
        int nn=m+n-1;
        while(m>0&&n>0)
        {
        if(nums1[m-1]<nums2[n-1])
         {
            nums1[nn]=nums2[n-1];
            n--;
            }
            else
            {
                nums1[nn]=nums1[m-1];
                m--;
            }
            nn--;
        }
        //nn所在位置现在是,m与n的差
        //比如[2,3,4,0,0,0,0]
            //[3,3,4,5]
        //此时数组是[2,3,3,3,4,4,5],最后时刻2还在第一个,然后n也等于0了。m!=0没事,会在正确的位置放置。
        //再比如[4,5,6,0,0,0]
        //[1,2,3]
        //此时数组为[4,5,6,4,5,6],n=3,nn=2,这样就一个一个将nums2剩余的元素放入nums1中,要注意放入的顺序也是倒序放入。
        while(n>0)
        {
            nums1[nn]=nums2[n-1];
            n--;
            nn--;
        }
    }
}

6.环形链表

题目链接
在这里插入图片描述

思路

本题也是有个比较愚蠢的方法:通过使用HashSet来辅助我们寻找,只要有环就说明一定有一个节点(环的入口)是到达了两次,我们使用HashSet去判断当前的节点是否存在,如果不存在就添加进HashSet,如果存在就说明有环。
不借助额外空间的方法:我们使用快慢指针,快指针每次移动两下,慢指针一次移动一下,如果慢指针与快指针相遇则说明该链表存在环。那么如何让循环结束呢(不存在环)我们可以判断如果fastnull或者fast.nextnull,则说明不存在环。这里为什么要判断两个呢,因为fast每次都要移动两下,假如当前fast已经指向链表的最后一个节点了,再让fast指向fast.next.next就会报错,因为fast.next为空,空不能再去寻找其next。

代码

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
//试试快慢指针
public class Solution {
    public boolean hasCycle(ListNode head) {
        if(head==null||head.next==null)
        {
            return false;
        }
        ListNode fast=head.next;
        ListNode slow=head;
       while(fast!=slow)
       {
         if(fast==null||fast.next==null)
               return false; 
        fast=fast.next.next;
        slow=slow.next;
       }
        return true;
    }
}

7.通过删除字母匹配到字典里最长单词

在这里插入图片描述

思路

首先说如何判断s能否通过删除一些字母来匹配d中的一个字符串。
我们使用一个指针left指向s的第一位,用另一个指针right指向d的第一位,我们每次都判断s的left位是否等于d1的right位,如果相等同时移动left和right,同时判断right是否指向了d1的最后一位;如果不相等则移动left,直至left指向s的最后一位。
我们按照该思路写一个函数,把d中的所有字符串都与s比较。如果答案不止一个,我们要去找长度最长且字典顺序最小的字符串,我们使用一个String对象result来存储我们的最长的字符串,如果d中的字符串与s比较后可以通过删除字符来匹配,那就将这个字符串与result来比较,维护这个result,最后返回。

代码

比较长度比较好说。当长度相等时,如何比较字典序呢,我们使用compareTo方法,该方法会挨个比较dd(d中满足条件的字符串)与result中的字符,直到二者不相同,返回二者的差值。a.compareTo(b)就说明a的字典序小于b

class Solution {
    public String findLongestWord(String s, List<String> d) {
        int maxlength = 0;
        String result = "";
        for(String dd:d)
        {
            if(find(s,dd))
            {
                if(dd.length()>maxlength)
                {
                    result = dd;
                    maxlength = dd.length();
                }else if(dd.length()==maxlength)
                {
                    //长度相等
                    //留字典序最小的字符串
                    //compareTo会挨个比较dd与result中的字符,直到二者不相同,返回二者的差值
                    if(dd.compareTo(result)<0)
                    {
                          result = dd;
                    maxlength = dd.length();
                    }
                }
            }
        }
        return result;
    }
    public boolean find(String s,String d)
    {
        int left = 0;
        int right = 0;
        for(;left<s.length();left++)
        {
            if(s.charAt(left)==d.charAt(right))
                right++;
            if(right==d.length())
                return true;
        }
        return false;

    }
}

滑动窗口

问题简介与模板代码

简介

来源:五分钟学代码

滑动窗口这类问题一般需要用到 双指针 来进行求解,另外一类比较特殊则是需要用到特定的数据结构,像是 sorted_map。

后者有特定的题型,后面会列出来,但是,对于前者,题形变化非常的大,一般都是基于字符串和数组的,所以我们重点总结这种基于双指针的滑动窗口问题。

题目问法大致有这几种:

  • 给两个字符串,一长一短,问其中短的是否在长的中满足一定的条件存在,例如:
  • 求长的的最短子串,该子串必须涵盖短的的所有字符
  • 短的的 字符串 在长的中出现的所有位置
  • 给一个字符串或者数组,问这个字符串的子串或者子数组是否满足一定的条件,例如:
  • 含有少于 k 个不同字符的最长子串
  • 所有字符都只出现一次的最长子串

除此之外,还有一些其他的问法,但是不变的是,这类题目脱离不开主串(主数组)和子串(子数组)的关系,要求的时间复杂度往往是 O(n) ,空间复杂度往往是常数级的。

之所以是滑动窗口,是因为,遍历的时候,两个指针一前一后夹着的子串(子数组)类似一个窗口,这个窗口大小和范围会随着前后指针的移动发生变化。
在这里插入图片描述

模板代码

根据前面的描述,滑动窗口就是这类题目的重点,换句话说,窗口的移动 就是重点!

我们要控制前后指针的移动来控制窗口,这样的移动是有条件的,也就是要想清楚在什么情况下移动,在什么情况下保持不变。

思路是保证右指针每次往前移动一格,每次移动都会有新的一个元素进入窗口,这时条件可能就会发生改变,然后根据当前条件来决定左指针是否移动,以及移动多少格

我写来一个模版在这里,可以参考:

//Editor:程序员小吴
public int slidingWindowTemplate(String[] a, ...) {
    // 输入参数有效性判断
    if (...) {
        ...
    }

    // 申请一个散列,用于记录窗口中具体元素的个数情况
    // 这里用数组的形式呈现,也可以考虑其他数据结构
    int[] hash = new int[...];

    // 预处理(可省), 一般情况是改变 hash
    ...

    // l 表示左指针
    // count 记录当前的条件,具体根据题目要求来定义
    // result 用来存放结果
    int l = 0, count = ..., result = ...;
    for (int r = 0; r < A.length; ++r) {
        // 更新新元素在散列中的数量
        hash[A[r]]--;

        // 根据窗口的变更结果来改变条件值
        if (hash[A[r]] == ...) {
            count++;
        }

        // 如果当前条件不满足,移动左指针直至条件满足为止
        while (count > K || ...) {
            ...
            if (...) {
                count--;
            }
            hash[A[l]]++;
            l++;
        }

        // 更新结果
        results = ...
    }

    return results;
}

这里面的 “移动左指针直至条件满足” 部分,需要具体题目具体分析,其他部分的变化不大。

固定长度滑动窗口

固定长度滑动窗口的题目核心就是维护一个长度固定的滑动窗口,当滑动窗口的长度大于固定值时,右移left指针。

1.找到字符串中所有字母异位词

在这里插入图片描述

思路

滑动窗口的题目比较难,先来分析一下题目,异位词就是说只要子串中的字符与p中字符相同即可,不要求与p中字符顺序相同。

根据模板,我们需要创建一个hash数组用来存储当前窗口中的各字符的数量,我们可以先将p中的字符放入hash数组,以便我们进行处理。

同时,需要创建一个count变量来存储窗口中满足条件的字符个数(比如p为abc,窗口中进来了a,那么count就加1,但窗口又进来一个a,count就不能再加1了,因为p中只有1个a)。

初始时,让right指针向后走,并不断更新hash数组(每进来一个字符,就将hash[right字符]减一)。

那我们什么时候该移动left指针呢?本题为固定窗口长度,因此可以当窗口的长度大于p的长度时,移动left指针。

当窗口的长度大于p的长度时,我们移动left指针,同时将hash[left字符]加1,因为left字符已经不在窗口中了。同时判断left对应的字符是否在窗口中被需要(也不是单纯判断是否在p中,是判断窗口中是否需要这个left对应的字符),如果被需要还要将count减1。通过判断count是否等于p的字符串长度来判断是否找到了一个字母异位词字符串。

核心思路:我们维护一个长度为p的滑动窗口,每次窗口进入一个新字符,都要在hash数组中更新该字符对应的数量(减1),之后当hash数组中该字符的数量大于等于0(说明之前hash数组中存在该字符,也就是说p中还剩余该字符,进一步说明当前窗口需要该字符)就将count减一。当窗口长度大于p长度时,我们要将left对应的字符删去,同时在hash数组中更新该字符对应的数量(加1),如果hash数组中该字符的数量大于0,则说明当前窗口需要该字符,就将count减一。判断count的数量是否等于p的长度,如果等於则将left指针对应的位置添加进入results。

代码

这里要注意一下当移动left时,什么情况下需要将count减1。

class Solution {
   public List<Integer> findAnagrams(String s, String p) {
    // 输入参数有效性判断
    if (s.length() < p.length()) {
        return new ArrayList<Integer>();
    }

    // 申请一个散列,用于记录窗口中具体元素的个数情况
    // 这里用数组的形式呈现,也可以考虑其他数据结构
    char[] sArr = s.toCharArray();
    char[] pArr = p.toCharArray();

    int[] hash = new int[26];

    for (int i = 0; i < pArr.length; ++i) {
        hash[pArr[i] - 'a']++;
    }

    // l 表示左指针
    // count 记录当前的条件,具体根据题目要求来定义
    // result 用来存放结果
    List<Integer> results = new ArrayList<>();
    int l = 0, count = 0, pLength = p.length();

    for (int r = 0; r < sArr.length; ++r) {
        // 更新新元素在散列中的数量
        hash[sArr[r] - 'a']--;

        // 根据窗口的变更结果来改变条件值
        if (hash[sArr[r] - 'a'] >= 0) {
            count++;
        }

        if(r-l+1<pLength)
            continue;
        else if(r-l+1>pLength)
        {
            hash[sArr[l]-'a']++;
            //可以理解为当长度超过p时,新增的一个元素如果和头的元素一样且都是p中的字符的话,count就不需要减
            //比如数组为s:abab,p:ab,经历ab后,hash[0]=0,hash[1]=0,count=2,这里对应a和b
            //当r到2位置(a)时,hash[0]--;此时hash[0]=-1,故count不变,此时窗口长度大于p的长度,因此会hash[0]++(因为滑动窗口去掉了l对应的元素,l对应a)
            //此时hash[0]=0,故count不需要自减,此时count=2,所以会把1也加入list中。
            //那么这里为什么不直接判断当前r的元素是否和l的元素相等呢,原因在于,即使r元素和l元素相等也存在r元素和l元素不是p字符串中的字母
            //既然不是p中的字母也就不能减去这一次count了。而当l和r中的元素相等且不是p中的字符会出现这种情况,
            //以cbcb,ab为例,当走到第二个c时,hash[c]起初为-2,加上1之后仍为-1,因此可见只要是不存在p中的,即使再遇见与l相同的也不会进入该判断中。
            if(hash[sArr[l]-'a']>0)
            {
                count--;
                }
            l++;
        }
        // 更新结果
        if (count == pLength) {
            results.add(l);
        }
    }

    return results;
    }
}

2.字符串的排列

题目链接
在这里插入图片描述

思路

与上一道题相似,具体分析可以看上一道题,然后本题可以检验下上一道题是否完整的学会了,我们使用HashMap来存储当前窗口需要的字符个数。

代码

class Solution {
    public boolean checkInclusion(String s1, String s2) {
      //固定长度滑动窗口
      int N = s1.length();
      char[]cs2 = s2.toCharArray();
      //
        Map<Character,Integer>map = new HashMap<>();
        //将s1的值放入map
        for(int i=0;i<N;i++)
        {
            map.put(s1.charAt(i),map.getOrDefault(s1.charAt(i),0)+1);
        }
        int left = 0;
        int right = 0;
        int now = 0;
        for(;right<cs2.length;right++)
        {
            if(!map.containsKey(cs2[right]))
            {
                map.put(cs2[right],-1);
            }else{
                 map.put(cs2[right],map.get(cs2[right])-1);
                if(map.get(cs2[right])>=0)
                {
                    now++;
                }
            }
            if(right-left+1>N)
            {
                map.put(cs2[left],map.get(cs2[left])+1);
                //当前滑动窗口需要该字符
                if(map.get(cs2[left])>0)
                {
                    now--;
                }
                left++;
            }
            if(now==N)
                return true;
        }
        return false;
    }
}

3. 长度为 K 的无重复字符子串

添加链接描述
在这里插入图片描述

思路

我们维护一个长度为K的滑动窗口,当长度超过K的时候,就要右移left指针,当加进窗口的字符存在于滑动窗口时,就要不断右移left指针,直到left指针指向了滑动窗口中第一次出现的该字符,然后再右移一次left指针,将其移出,以便让当前right指针指向的字符加入滑动窗口。

代码

class Solution {
    public int numKLenSubstrNoRepeats(String S, int K) {
        //
        char[]ss = S.toCharArray();
        int left = 0;
        int right =0;
        //滑动窗口中各字母个数
        int[]hash = new int[26];
        int times = 0;
        for(;right<ss.length;right++)
        {
            char now = ss[right];
            hash[now-'a']++;
            //有重复的了,要在窗口中去掉当前的now字符
            if(hash[now-'a']>1)
            {
                //这里和平常的不一样这里是hash[now-'a']>1。
                while(left<right&&hash[now-'a']>1)
                {
                    //这里在滑动窗口中去掉当前left指向的字符
                    hash[ss[left]-'a']--;
                    left++;
                }
            }
            //比如K为5,但是前6个都没有重复的,那么就left右移
            if(right-left+1>K)
            {
                  hash[ss[left]-'a']--;
                    left++;
            }
            if(right-left+1==K)
                times++;
        }
        return times;
    }
}

可变长度滑动窗口

这种情况下left指针的移动就要看是否不满足什么条件了。

1.最小覆盖子串

题目链接
在这里插入图片描述

思路

明显是要使用滑动窗口,比较复杂的一点是,滑动窗口的长度是可变的。所以区别于固定滑动窗口的达到窗口长度后随着右指针向右移动一位左指针也要移动一位

滑动窗口更改长度的原因是在滑动窗口中去掉不需要的字符

举个例子:S=ADOBECODEBANC,T=ABC,首先一直向后走,

此时滑动窗口中为ADOBECODEB,hash数组中hash[A]=0,hash[B]=-1,hash[C]=0,hash[D]=-2,hash[E]=-2,hash[O]=-1,

继续向右走,当我们右指针走到第二个A的时候ss[left]=-1,就应该把第一个A去掉,同时,要去掉T中没有的:DO,也去掉T中虽然有但我们后面(滑动窗口中)已经有了的:B,之后继续去掉T中不需要的:E,这时left应该指向C,然后就不继续动了。

此时滑动窗口中字符串为CODEBA,right继续向后走

走到N无所谓,走到C时,又要继续挪动我们的滑动窗口,同理,去掉CODE。于是此时滑动窗口为BANC。

举个另一个的例子,S=DOBECODEBANC,T=ABC,当left指向D,right指向O时,(left指向的也是T中不需要的,所以应该右移左指针),

以代码来看:
hash[D]=-1,因此也要让left右移,直到找到一个滑动窗口中需要的字符或left已经等于right

核心思想:始终让left指向T中需要的字符或者已经指向了right。

注意这里是可以使用HashMap来做的。还有我们为什么在滑动时不需要对count进行计算,因为滑动过程中不会滑动出去当前T中需要的字符。

S=ADOBECODEBANC,T=ABC
当指向第二个A时,left会一直滑动,直到遇到第二个B(因为碰到第一个B时hash['B']=-1,
可以看出滑动过程中滑出去的都是T中不需要的,比如D(不在T中),比如B(后面还有一个),
因此滑动过程中不会导致count变化。
同时假如后面没有第二个B,那么滑到第一个B时就停止滑动了。

代码

class Solution {
    public String minWindow(String s, String t) {
        
        char[] ss = s.toCharArray();
        char[] tt = t.toCharArray();
        //用来存储滑动窗口中的值(注意还有小写的)
        int[]hash = new int[256];
        //最小子串的长度
        int minlength = s.length();
        //最小子串
        String results = "";
        for(char smallt:tt)
        {
            hash[smallt-'0']++;
        }
        int left = 0;
        int right = 0;
        int count = tt.length;
        for(;right<ss.length;right++)
         {
             hash[ss[right]-'0']--;
             //说明当前的字符存在于T中,且当前滑动窗口中还需要该字符
             //后面这个且的意思就是比如我的T为ABC,我只有第一次遇到A才会count--,而第二次就不会了,
             if(hash[ss[right]-'0']>=0)
             {
                count--;
             }
             //right又遇到了left处的字符(特指遇到T中存在的),或left处的字符不是T中需要的,就右移左指针直到找到需要的或者left=right
             while(left<right&&hash[ss[left]-'0']<0)
             {
                hash[ss[left]-'0']++;
                left++; 
             }
            //这里大于等于是防止最小覆盖子串就是s其本身
            if(count==0&&minlength>=right-left+1)
            {
                minlength = right-left+1;
                results = s.substring(left,right+1);
            }
         }
         return results;
    
    }
}

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

在这里插入图片描述

思路

本题相对来说比较简单,我们窗口移动的条件是:当前right指向的字符存在于窗口中时,需要移动left直至当前right指向的字符不存在于当前窗口中。

我们这里不使用数组来记录,我们使用一个HashSet,当right指针指向的字符已经存在于HashSet中时,我们就要移动left指针,直至left指针指向的字符等于right指针指向的字符,然后left再向右一次,就可以将right指针指向的字符添加进入窗口。要注意的是在left指针移动的过程中要在HashSet中删除left指针对应的那个字符。不用考虑我删除了当前left对应的字符,万一窗口后面还有这个字符怎么办,不用担心,因为窗口中的字符都是不重复的,所以只要窗口不包含当前left指向的字符,就可以在HashSet中删除该字符。

代码

class Solution {
    public int lengthOfLongestSubstring(String s) {
        //可变长度滑动窗口
        //窗口移动的条件:当前right指向的字符存在于窗口中,需要移动left直至当前right指向的字符不存在于当前窗口中
        int max = 0;
        char[]ss = s.toCharArray();
        if(s==null||s.length()==0||s=="")
            return max;
        int left = 0;
        int right = 0;
        Set<Character>set = new HashSet<>();
        for(;right<s.length();right++)
        {
            if(set.contains(ss[right]))
            {
                while(left<right&&ss[left]!=ss[right])
                {
                    set.remove(ss[left]);
                    left++;
                }
                //left向后移一位,因为当前left指向的是与right相等的那个字符
                //这里没有remove与right相等的那个字符
                left++;
            }
           else{
            set.add(ss[right]);
           }
            max = Math.max(right-left+1,max);
        }
        return max;
    }
}

3.最大连续1的个数 III

在这里插入图片描述

思路

求最长子数组的长度,其实就可以用滑动窗口,维护一个滑动窗口,始终让窗口中都是1,和最多k个0。当0的个数超过k的时候,就要不断右移left指针,直至左指针指向0,在右移一位,将这个0移出窗口,以便将当前right的0加入窗口。

代码

class Solution {
    public int longestOnes(int[] A, int K) {
        //滑动数组
        int left = 0;
        int right = 0;
        int length = Integer.MIN_VALUE;
        int t = 0;
        for(;right<A.length;right++)
        {
            if(A[right]==1)
                ;
            else{
                if(t<K)
                    t++;
                else{
                    //已经全换完了
                    //length = Math.max(length,right-left );
                    //往后挪直到这个0可以加进滑动数组
                    while(left<right&&A[left]==1)
                    {
                        left++;
                    }
                    //left指向0后面这个值
                    left++;
                }
            }
            //有可能数组中0的个数小于K,所以不能只在换完了判断一下最长长度。
            length = Math.max(length,right-left+1);
        }
        return length;
    }
}

4.至多包含两个不同字符的最长子串

题目链接
在这里插入图片描述

思路

其实思路很清晰,让滑动窗口中的不同字符小于2,我们可以使用HashMap来存储各个字符在窗口中的个数,初始想法是让right指向的字符添加进map,当map的size(映射数)超过2,就去掉滑动窗口中所有当前left指向的字母才能添加进来当前right指向的字母。核心代码如下:

	char leftchar = ss[left];
  while(hash[leftchar-'0']>0)
            	{
                    hash[ss[left]-'0']--;
                    left++;
                }
                set.remove(leftchar);

但是这个思路会有问题:
比如例子为"abaccc",当right指向c时,map的size超过2,而我们想让left指的字母(a)移出滑动窗口,需要最终让left指向c,
但问题是left移动的过程中,把b也去掉了,也就是说当left指向第二个a的时候,其实滑动窗口中已经变成了只包含两个不同字符(a,c)所以我们滑动窗口要保证的是时刻让滑动窗口中最多只有两个不同的字符。
最后当我们移动left指针时,想要在map中删除当前字符,要判断一下在map中当前字符对应的数量是否为0。

代码

class Solution {
    public int lengthOfLongestSubstringTwoDistinct(String s) {
        //Hashmap也有size(),可以返回此映射中的关系数
        //滑动数组
        char[] ss = s.toCharArray();
        int left = 0;
        int right = 0;
        Map<Character,Integer>map = new HashMap<>();
        //注意还有大写的
        //int[]hash = new int[720];
        //Set<Character>set = new HashSet<>();
        int max = 0;
        for(;right<ss.length;right++)
        {
            map.put(ss[right],map.getOrDefault(ss[right],0)+1);
            if(map.size()>2)
            {
                /*
                需要左移
                //char leftchar = ss[left];
                //要去掉滑动窗口中所有当前left指向的字母才能添加进来当前right指向的字母
                //注意这里不是hash[ss[left]-'a']
                while(hash[leftchar-'0']>0)
                {
                    hash[ss[left]-'0']--;
                    left++;
                }
                set.remove(leftchar);
                */
                 //这里又有新的问题了,比如例子为"abaccc"我们想让left指的字母(a)移出滑动窗口,需要left指向c,但问题是left指向的过程中,把b也去掉了,也就是说当left指向第二个a的时候,其实滑动窗口中已经变成了只包含两个不同字符(a,c)所以我们滑动窗口要保证的是时刻让滑动窗口中最多只有两个不同的字符。
                 while(map.size()>2)
                 {
                     char leftchar = ss[left];
                     int num = map.get(leftchar);
                     if(num==1)
                     {
                         map.remove(leftchar);
                     }else{
                         map.put(leftchar,num-1);
                     }
                     left++;
                 }
            }
            max = Math.max(max,right-left+1);
        }
        return max;
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章