算法題:查找一個字符串中最長不含重複字符的子字符串,計算該最長子字符串的長度;下面將使用 滑動窗口 方法實現,並經過對滑動窗口算法一步步進行優化,使其空間和時間的消耗一步步下降;java
滑動窗口:通常是指 運行在一個大數組上的子數組,該大數組是一個底層元素集合 。例如:假設有大數組 [ a b c d b e f d n ] ,設定一個大小爲 3 的小數組 爲 滑動窗口 ;則存在下面的窗口:算法
[a b c] [b c d] [c d b] [d b e] [b e f] [e f d] [f d n]
滑動窗口重要性質:數組
- 滑動窗口通常表示成一個 左閉右開區間 。
- 窗口的左邊界(指針 i)和右邊界(指針 j)永遠只能向右移動 ,而不能向左移動。
如圖:學習
沒有任何優化的滑動窗口實現;優化
經過 指針 i 和 指針 j 不斷的向左移動,造成了一個個的窗口,而且在將窗口的字符存放到了 Set 集合中,使用 Set 集合判斷 即將 進入窗口中的字符(也就是指針 j 移動到指向的字符)是否在窗口已經存在;spa
- 若是已經存在:則計算此時窗口的大小,並將存放窗口字符的 Set 集合清空(清空是爲了存放下個窗口的字符),最後將 指針 i 向左移動一位,而後指針 j 也指向指針 i 的位置。
- 若是是不存在:則將此字符存放到 Set 集合窗口字符中 。
public class LeetCode { public static int lengthOfLongestSubstring(String s) { if (s == null){ return 0; } if (s.length() == 1){ return 1; } // set 用來存儲窗口的字符 Set<Character> set = new HashSet(); // 指針i int i = 0; // 指針j int j = i; // 最大長度 int max = 0; char[] sc = s.toCharArray(); while(j < sc.length && i <= j){ // 當字符沒在窗口中 if (!set.contains(sc[j])){ set.add(sc[j]); // 指針j 移動 j++; }else { // 若是字符在窗口中時, 獲得當前窗口中的字符個數 int size = set.size(); if (max < size){ max = size; } // 將set中存儲的字符清空 set.clear(); // 指針i 移動 i++; // 指針j 移動到指針i 的位置 j = i; } } // 當指針j 移動到字符串尾部時, 窗口中可能還存在字符 if (set.size() > max){ max = set.size(); set.clear(); set = null; // help GC } return max; } public static void main(String[] args) { System.out.println(lengthOfLongestSubstring("abcdbefdn")); } }
缺點一:存在不少無用的重複的 滑動窗口 ;指針
例如:字符串 abcdbefdn,根據上面實現的滑動窗口方法,會獲得如下窗口:[ a b c d ]、[ b c d ]、[ c d b e f ]、[ d b e f ]、[ b e f d n ] 這 5 個滑動窗口;下面錯位展現更直觀,會發現 [ b c d ]、 [ d b e f ] 這兩個滑動窗口顯然被包含在其以前的窗口中,它們被重複統計了。code
[ a b c d ] [ b c d ] [ c d b e f ] [ d b e f ] [ b e f d n ]
缺點二:存儲 滑動窗口中 字符的 Set 集合存在反覆 清空,再次存入字符的狀況;而且存在字符被重複存入 Set 集合中。blog
優化點:rem
- 直接將指針 i 指向出現的重複字符的位置,滑動窗口大小爲 (j - i),這樣就將無用的重複的 滑動窗口 跳過,這樣會大大縮短執行時間;
- 存放滑動窗口的字符容器改成 Map 集合,key爲 字符,value 爲字符下標;而且再也不清空集合了,而是遇到重複字符後,更新此字符的下標位置;
例如:一開始 字符 b 在map集合中的value 位置爲1,當再次遇到下標爲 3 的字符 b 後,將map集合中的value 下標 由 1 改成 3 ;
public class LeetCode { public static int lengthOfLongestSubstring(String s) { if (s == null){ return 0; } if (s.length() == 1){ return 1; } // map 用來存儲窗口字符, key是字符, value爲字符在字符串中的下標位置 Map<Character, Integer> map = new HashMap<Character, Integer>(); // 指針j int j = 0; // 重複字符的位置, 默認爲-1 int i = -1; // 最大長度 int max = 0; char[] sc = s.toCharArray(); while(j < sc.length){ if (map.containsKey(sc[j])){ // 獲取map中重複字符的位置 int index = map.get(sc[j]); if (index > i){ i = index; } } // 指着j - 重複字符的位置 = 當前窗口的大小 if ((j-i) > max){ max = j-i; } // 若是map中存在重複字符的話,這裏是將字符的位置進行更新 ; 若是不是重複字符的話,就直接存放到map中 map.put(sc[j], j); j++; } map.clear(); map = null; // help GC return max; } public static void main(String[] args) { System.out.println(lengthOfLongestSubstring("abcdbefdn")); } }
上面就是通過了代碼優化後獲得的執行效果,發現執行時間大大縮短了;可是這可能還不是最優的,可能還存在最優的方法。
一切看文章不點贊都是「耍流氓」,嘿嘿ヾ(◍°∇°◍)ノ゙!開個玩笑,動一動你的小手,點贊就完事了,你每一個人出一份力量(點贊 + 評論)就會讓更多的學習者加入進來!很是感謝! ̄ω ̄=