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;
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章