日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

LeetCode算法题6:滑动窗口*

發布時間:2025/6/17 编程问答 16 豆豆
生活随笔 收集整理的這篇文章主要介紹了 LeetCode算法题6:滑动窗口* 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章目錄

  • 前言
  • 一、無重復字符的最長子串
    • 思路 1:
    • 思路 2:
  • 二、字符串的排列
    • 思路 1:
    • 思路 2:
    • 思路 3:
    • 思路 3 plus:
    • 思路 3 plus plus:
    • 思路 4:
    • 思路 5:
  • 總結


前言

??????Leetcode算法系列:https://leetcode-cn.com/study-plan/algorithms/?progress=njjhkd2

??????簡單總結一下滑動窗口相關的算法題:

一、無重復字符的最長子串

??????題目鏈接:https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/

??????題目描述:給定一個字符串 s ,請你找出其中不含有重復字符的 最長子串 的長度。

思路 1:

??????用一個區間(窗口)來表示當前的無重復字符串,每當區間擴大1位的時候,需要判斷當前擴充的元素和區間中哪位元素發生重復,不發生重復,區間長度加一;否則從重復元素的下一個元素作為區間的起始點,當前擴充的元素作為區間的結束點。依次遍歷所有元素。

public int lengthOfLongestSubstring(String s) {int i=0,end=s.length(),start=0;//i 遍歷整個字符串;start 為區間起始點。int tmp=0,ans=0;//tmp 為當前合法區間的長度,ans 為以往合法區間長度的最大值。while(i<end){int re=isNotDup(s,start,i);if(re==-1)tmp++;else{ans=tmp>ans?tmp:ans;tmp=i-re+1;start=re;//設置起點。}i++;}return ans>tmp?ans:tmp;}public int isNotDup(String a,int i,int j){while(i<j){if(a.charAt(i)==a.charAt(j))return i+1;i++;}return -1;}

??????時間復雜度為 O(n2)。

思路 2:

??????關于如何判斷是否發生重復,以及設置區間的下一個起點,可以采用 Set 來做,如果發生重復的話:從上一個區間的起點依次將元素移出集合,直到不重復為止。參考算法如下:

public int lengthOfLongestSubstring(String s) {Set<Character> container=new HashSet<>();int i=0,end=s.length(),start=0;int tmp=0,ans=0;while(i<end){if(!container.add(s.charAt(i))){tmp=container.size();ans=ans<tmp?tmp:ans;for(int j=start;j<i;j++){container.remove(s.charAt(j));if(container.add(s.charAt(i))){start=j+1;break;}}}i++;}return ans>container.size()?ans:container.size();}

??????時間復雜度為 O(n2)。但是它的執行效率沒有算法1 快。

二、字符串的排列

??????題目鏈接:https://leetcode-cn.com/problems/permutation-in-string/

??????題目描述:給你兩個字符串 s1 和 s2 ,寫一個函數來判斷 s2 是否包含 s1 的排列。如果是,返回 true ;否則,返回 false 。

換句話說,s1 的排列之一是 s2 的 子串 。

思路 1:

??????直觀上,對 s1 求得全排列,然后分別對 s1 的所有排列驗證是 s2 的子串。為了回顧 Kmp 和 全排列算法,參考代碼如下:(但它的時間復雜度太高,在此不建議使用)

public boolean checkInclusion(String s1, String s2) {//方法1:得到s1的全排列組合, 再采用kmp判斷每個s1是否為s2的子串。 超出內存限制if(s1.length()>s2.length())return false;ArrayList<String> re=new ArrayList<>();arrange(re,s1.toCharArray(),0,s1.length()-1);for(String tmp:re){if(kmp(tmp,s2))return true;}return false;}public static boolean kmp(String s1,String s2){ //s1是否為s2的子串//求next數組。// -1表示用s1中的第一個字符匹配s2的下一個字符;非負數表示用s1對應next中的值再次匹配當前值。int len=s1.length();int[] next=new int[len];next[0]=-1;int i=0,tmp;while(++i<len){tmp=next[i-1];if(tmp==-1){//簡化:不需要考慮 s1(0) 和 s1(1) 相等的情況。next[i]=0;continue;}while(tmp!=-1&&s1.charAt(i-1)!=s1.charAt(tmp))tmp=next[tmp];next[i]=tmp+1;}//開始匹配int index1=0,index2=0;while(index2<s2.length()){while(s2.charAt(index2)!=s1.charAt(index1)){index1=next[index1];if(index1==-1)break;}index2++;index1++;if(index1==len)return true;}return false;}//全排列:public void arrange(ArrayList<String> re,char[] a,int start,int end){if(start==end)re.add(new String(a)); //采用a構建一個String對象。else {int index=start;char tmp;while(index<=end){tmp=a[index];a[index]=a[start];a[start]=tmp;arrange(re,a,start+1,end);tmp=a[index];a[index]=a[start];a[start]=tmp;index++;}}}

思路 2:

??????以 s1 的第一個字符為基準,找到該字符在 s2 中每一次出現的位置 a0、a1、a2… ,且假設 s1 的長度為 len,那么在 a0 處從區間【a0-len+1,a0】直到【a0,a0+len-1】都有可能是 s1 的一個排列,其余的 a1,a2也是一樣。對區間【a0-len+1,a0】直到【a0,a0+len-1】中的元素分別進行排序和排序后的 s1 比較判斷是否相等。參考算法如下(其時間復雜度依然太高):

public boolean checkInclusion(String s1, String s2) {int len1=s1.length(),len2=s2.length();if(len1>len2)return false;int start,end;for(int j=0;j<len2;j++){if(s1.charAt(0)==s2.charAt(j)){//從最左區間開始判斷start=j-len1+1;end=j;if(start<0){end=len1-1;start=0;}for(;end<len2;start++,end++){if(decide(s1,s2.substring(start,end+1)))return true;}}}return false;}public boolean decide(String a,String b){char[] aa=a.toCharArray();char[] bb=b.toCharArray();Arrays.sort(aa);Arrays.sort(bb);if(Arrays.equals(aa,bb))return true;return false;}

思路 3:

??????在 2 的基礎上,對區間【a0-len+1,a0】直到【a0,a0+len-1】中的元素和 s1 的比較采用統計字符個數的方法來判斷是否相等。參考算法如下(其時間復雜度也比較高):

public boolean checkInclusion(String s1, String s2) {int len1=s1.length(),len2=s2.length();if(len1>len2)return false;int start,end;int[] count1=new int[26];int[] count2=new int[26];for(int j=0;j<len2;j++){if(s1.charAt(0)==s2.charAt(j)){//從最左區間開始判斷start=j-len1+1;end=j;if(start<0){end=len1-1;start=0;}for(;end<len2;start++,end++){Arrays.fill(count1,0);Arrays.fill(count2,0);if(decide(s1,s2.substring(start,end+1),count1,count2))return true;}}}return false;}public boolean decide(String a,String b,int[] count1,int[] count2){//采用判斷字符的個數是否相同的方法驗證a是否為b的某個排列。for(int i=0;i<a.length();i++){count1[a.charAt(i)-'a']++;count2[b.charAt(i)-'a']++;}if(Arrays.equals(count1,count2))return true;return false;}

思路 3 plus:

??????首先沒必要每次都初始化數組 count1,它只需要初始化一次即可,其次對于 count2 沒必要進行整體上的改變,每次只需要增加新元素的個數,減少舊元素的個數。參考算法如下:

public boolean checkInclusion(String s1, String s2) {int len1=s1.length(),len2=s2.length();if(len1>len2)return false;int start,end;int[] count1=new int[26];for(int i=0;i<len1;i++)count1[s1.charAt(i)-'a']++;int[] count2=new int[26];for(int j=0;j<len2;j++){if(s1.charAt(0)==s2.charAt(j)){//從最左區間開始判斷start=j-len1+1;end=j;if(start<0){end=len1-1;start=0;}//count2 初始化:Arrays.fill(count2,0);for(int i=start;i<=end;i++)count2[s2.charAt(i)-'a']++;if(Arrays.equals(count1,count2))return true; int tmp=j+len1-1;for(start++,end++;end<len2&&end<=tmp;start++,end++){count2[s2.charAt(end)-'a']++;count2[s2.charAt(start-1)-'a']--;if(Arrays.equals(count1,count2))return true; }}}return false;}

思路 3 plus plus:

??????上面算法雖然相比于之前的算法有了更好的運行效率。但是此算法的效率沒有直接在字符串 s2 上采用滑動窗口的方法高,原因在于上面的算法在區間有重復計算和比較。在 s2 直接采用滑動窗口的算法參考如下:

public boolean checkInclusion(String s1, String s2) {int n = s1.length(), m = s2.length();if (n > m) {return false;}int[] cnt1 = new int[26];int[] cnt2 = new int[26];for (int i = 0; i < n; ++i) {++cnt1[s1.charAt(i) - 'a'];++cnt2[s2.charAt(i) - 'a'];}if (Arrays.equals(cnt1, cnt2)) {return true;}for (int i = n; i < m; ++i) {++cnt2[s2.charAt(i) - 'a'];--cnt2[s2.charAt(i - n) - 'a'];if (Arrays.equals(cnt1, cnt2)) {return true;}}return false;}

思路 4:

??????上面算法有一個缺點:因為 ct2 每次各增減一個字符的個數,但是卻對 ct1 和 ct2 整體作比較,優化一下,那么需要用一個變量 diff 來表示 ct1 和 ct2 之間的差異性,如果 diff 為 0,則二者相等,返回 true;如果不相等,需要繼續在 s2 上進行增減一個字符操作。差異性可以用 ct2 - ct1 得到,如果相減之后的數組元素均為 0,則二者相等,diff 也為0,否則初始時的 diff 為相減之后數組元素不為 0 的個數。

??????這個數組用 cnt 來表示。當結果返回 true 時對應的是 cnt 中的元素均為 0(diff 為 0),如果 cnt 中的數為負,意味 ct2 中的字符沒有對應的 ct1 中的字符,則需要增加一個對應的字符;如果為正,意味著 ct2 中的字符是多余的,需要減去一個對應的字符。

??????在每次窗口滑動時,增加一個字符需要判斷 cnt[該字符]+1 之后是否為 0,如果為 0,令 diff 減一;減少的字符同理。如果 diff 為 0,證明已找到結果了。

??????最終:參考算法:

public boolean checkInclusion(String s1, String s2) {int len1=s1.length(),len2=s2.length();if(len1>len2)return false;int[] cnt=new int[26];int diff=0;for(int i=0;i<len1;i++){cnt[s2.charAt(i)-'a']++;cnt[s1.charAt(i)-'a']--;}for(int i=0;i<cnt.length;i++)if(cnt[i]!=0)diff++;if(diff==0)return true;for(int i=len1;i<len2;i++){int addIndex=s2.charAt(i)-'a',minusIndex=s2.charAt(i-len1)-'a';if(addIndex==minusIndex)continue;if(cnt[addIndex]==0)//如果原來的值為 0,diff需要加一。diff++;if(++cnt[addIndex]==0)//增加一個字符,對應個數加一。加完之后若為0,diff需要減一diff--;if(cnt[minusIndex]==0)diff++;if(--cnt[minusIndex]==0)diff--;if(diff==0)return true;}return false;}

思路 5:

??????在思路 4 的基礎上修改一下,用雙指針的思路來做,初始時 left 和 right 指針均為 0,表示僅包含在 s2 上的第一個元素。初始化時 cnt 數組中的所有值要不為 0,要不小于 0(僅用 cnt[s1.charAt(i)-‘a’]–;來初始化)。

??????遍歷:右指針一次遍歷,增加的元素如果令對應 cnt 中的數為 <= 0,繼續;如果 >0 時,則在當前左右指針所構成的區間中,當前元素是多余的,此時需要讓左指針不斷右移,去掉一個當前元素。

??????返回 true 的條件是:某一時刻 左右指針所構成的區間長度等于 s1。參考算法:

public boolean checkInclusion(String s1, String s2) {int len1=s1.length(),len2=s2.length();if(len1>len2)return false;int[] cnt=new int[26];int left=0,right=0,addIndex;for(int i=0;i<len1;i++)cnt[s1.charAt(i)-'a']--;while(right<len2){addIndex=s2.charAt(right)-'a';++cnt[addIndex];while(cnt[addIndex]>0){cnt[s2.charAt(left)-'a']--;left++;}if(right-left+1==len1)return true;right++;}return false;}

?????? 效率上,最后的雙指針要優于之前的解法,算法也比較簡潔。


總結

??????完。

總結

以上是生活随笔為你收集整理的LeetCode算法题6:滑动窗口*的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。