算法题:查找一个字符串中最长不含重复字符的子字符串,计算该最长子字符串的长度;下面将使用 滑动窗口 方法实现,并经过对滑动窗口算法一步步进行优化,使其空间和时间的消耗一步步下降;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")); } }
上面就是通过了代码优化后获得的执行效果,发现执行时间大大缩短了;可是这可能还不是最优的,可能还存在最优的方法。
一切看文章不点赞都是“耍流氓”,嘿嘿ヾ(◍°∇°◍)ノ゙!开个玩笑,动一动你的小手,点赞就完事了,你每一个人出一份力量(点赞 + 评论)就会让更多的学习者加入进来!很是感谢! ̄ω ̄=