给定一个字符串 s
,请你找出其中不含有重复字符的 最长子串 的长度。
示例 1:
输入: s = "abcabcbb" 输出: 3 解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
示例 2:
输入: s = "bbbbb" 输出: 1 解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
示例 3:
输入: s = "pwwkew" 输出: 3 解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。 请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
示例 4:
输入: s = "" 输出: 0
提示:
s
由英文字母、数字、符号和空格组成逐个生成子字符串
看它是否含有重复的字符
public int lengthOfLongestSubString(String s){ int n = s.length(); if(n<=1)return n; int maxLen = 1; for(int i=0;i<n;i++){ for(int j=i+1;j<n;j++){ if(allUnique(s,i,j)){ //判断i到j的区间中是否有重复的元素 maxLen = Math.max(maxLen,j-i+1);//如果不存在重复的元素则更新maxLen } } } return maxLen; } //同一个子串会进行多次判断是否存在重复字符 private boolean allUnique(String s,int start,int end){ Set<Character> set = new HashSet<>(); for(int i=start;i<=end;i++){ if(set.contains(s.charAt(i))){ //如果区间中存在重复的元素则返回false return false; } set.add(s.charAt(i)); } return true; //返回true没有重复的元素 }
时间复杂度为O(n3)
空间复杂度为O(n)
会超时
暴力求解存在重复计算,同一个子串会进行多次判断是否存在重复字符
优化:只需要判断s[i,j)中判断是否存在s[j]即可,如果存在s[j]则说明存在重复元素
核心思路:需要不断移动右边界,因为窗口越大越好,如果移动到某一个元素在当前窗口中已经存在,则需要移动左边界来缩小窗口,从而剔除掉重复的元素
定义两个边界left和right,第一个不存在重复元素,将右边界right往右边移动
右移后,此时窗口(子串)最大长度为2,更新maxLength
继续右移,此时right所指字符在之前窗口中已经存在,此时应该缩小窗口从而将重复字符剔除窗口,就需要移动左边界left
左边界移动后,窗口中依旧存在重复字符w,继续右移left
这时left和right指向同一个元素,窗口中没有重复字符,就需要右移right扩大窗口
右移right后,判断在窗口中没有重复字符,计算长度为2,和maxLenght相等,不用替换,继续右移右边界right
此时窗口中不存在重复元素e,此时窗口长度为3,大于之前的maxLength2,则需要更新maxLength,此时right继续右移
右移后,在窗口中已经存在相同元素w,则需要移动左边界left来缩小窗口剔除重复元素
这时left所指为k,在窗口中不存在重复元素,继续右移right
right移除字符串,结束循环
时间复杂度为O(n),每个字符最多遍历两遍
空间复杂度为O(n),需要将字符放在某种数据结构中,这种数据结构需要快速判断一个字符是否存在于这个窗口中,就需要一个HashSet来存储字符
public int lengthOfLongestSubString(String s){ int n = s.length(); if(n<=1)return n; int maxLen = 1; int left = 0,right = 0; //左边界和右边界 Set<Character> window = new HashSet<>(); //利用hashSet存储字符 while(right<n){ //处理窗口 char rightChar = s.charAt(right); //首先处理右边字符 //判断窗口是否包含右边字符,如果包含则需要移动左边界来缩小窗口剔除重复元素 while (window.contains(rightChar)){ window.remove(s.charAt(left)); left++; } maxLen = Math.max(maxLen,right-left+1); window.add(rightChar); right++; } return maxLen; }
优化:
以上当在窗口中存在重复字符,是一个一个字符的缩小窗口(一步一步的移动左边界left,效率较慢)
可以通过记住每个字符在字符串中的索引,当遇到重复字符的时候,就可以直接跳到重复字符的后面
可以使用HashMap存放遍历过的每个字符,来记录其位置
遍历字符串记录每个字符出现的位置,如果存在重复的元素,则将左边界left直接跳到重复元素的后面
此时w的索引位置变为最后一次的3,继续右移right
没有重复的元素则记录其索引,当移动到最后一位w,有重复元素,则拿到之前记录的w后一个元素的位置为4,将其赋给left,则可以快速剔除掉重复元素
public int lengthOfLongestSubString(String s){ int n = s.length(); if(n<=1)return n; int maxLen = 1; int left = 0,right = 0; //左边界和右边界 //利用hashMap存储字符的索引位置 Map<Charcter,Integer> window = new HashMap<>(); //处理窗口 while(right<n){ //首先处理右边字符 char rightChar = s.charAt(right); //如果rightChar之前在window中没有,则返回-1;如果存在则返回rightChar所在位置 int rightCharIndex = window.getOrDefault(rightChar,-1); //将当前right所指位置赋值+1给left if(rightCharIndex == -1) { // 无重复,不滑动 } else if(rightCharIndex < left) { // 脏数据,不滑动 // 因为我们在往前移动 left 的时候,并没有删除窗口 window 中的数据 // 所以,当我们从窗口中查找 rightChar 对应的元素的时候,就会找到它对应的索引是小于 left,也就是在 left 左边, // 因为这个字符在之前出现过。这种情况是不需要移动 left 的,因为在 left 到 right 的窗口内还没有重复字符的 } else { left = rightCharIndex + 1; } maxLen = Math.max(maxLen,right-left+1); window.put(rightChar,right); right++; } return maxLen; }
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/longest-substring-without-repeating-characters