给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。java
示例 1:bash
输入: "abcabcbb" 输出: 3 解释: 由于无重复字符的最长子串是 "abc",因此其长度为 3。
示例 2:测试
输入: "bbbbb" 输出: 1 解释: 由于无重复字符的最长子串是 "b",因此其长度为 1。
示例 3:优化
输入: "pwwkew" 输出: 3 解释: 由于无重复字符的最长子串是 "wke",因此其长度为 3。 请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
这道题的目标是找出最长子串,而且该子串必须不包含重复字符,并且这个子串必须是原字符串中连续的一部分(见示例3中的解释说明)。code
拿到题目时先不要心急想什么骚操做,咱们先从最普通的操做开始把题目解出来,而后再来看如何优化。blog
接下来,咱们画图分析一下,先随便弄一个长相普通的字符串:frankissohandsome
,咱们要从中找出咱们想要的子串,那少不了须要遍历,咱们设置两个变量from
,to
,分别存储寻找的目标子串在原字符串中的首尾位置。字符串
首先,from
和to
的初始值都为0(String的序号从0开始),子串长度length = 1
,最大子串长度maxLength = 1
。string
而后,咱们将to
的指向日后移动,并判断新遍历的字符是否已经存在于子串中,若是不存在,则将其加入子串中,并将length
进行自增。it
直到找到一个已存在于子串中的字符,或者to
到达字符串的末尾。这里,咱们找到了一个重复的s
,序号为7
,此时的子串为frankis
,将此时的子串长度与最大子串长度相比较(目前为0
),若是比最大子串长度大,则将最大子串长度设置为当前子串长度7
。io
接下来,咱们继续寻找符合条件的子串,这里比较关键的一点是下一个子串的起始位置,这里咱们将from
直接跳到了序号为7
的位置,由于包含ss
的子串显然都不能知足要求。
而后咱们依照以前的方法,找到第二个候选的子串sohand
,长度为6
,比目前的最大子串长度小,因此不是目标子串。
接着继续寻找,找到另外一个候选子串ohands
,长度小于最大子串长度,不是咱们的目标子串。
继续寻找。
to
到达了字符串末尾,找到另外一个候选子串handsome
,长度大于最大子串长度,这就是咱们的目标子串。
因而咱们的最大子串长度就轻松加愉快的找到了。接下来的事情就是把上面的思路转化成代码。
这里只须要注意一下from
的跳转便可,每次跳转的序号为to
指向的字符在子串中出现的位置 + 1。
class Solution { public int lengthOfLongestSubstring(String s) { if (s == null || s.length() == 0) return 0; int from = 0, to = 1, length = 1, maxLength = 1; // to遍历直到字符串末尾 while (to < s.length()){ int site = s.substring(from, to).indexOf(s.charAt(to)); if (site != -1){ // to指向的字符已存在 length = to - from; if (length > maxLength) maxLength = length; // from 跳转到site+1的位置 from = from + site + 1; } to++; } // 处理最后一个子串 if (to - from > maxLength) { maxLength = to - from; } return maxLength; } }
这里没有什么骚操做,考虑好边界状况就好了。有一个小细节须要注意,site
表明的是子串中字符出现的位置,不是原字符串中的位置,所以from
在跳转时,须要加上自身原来的序号。还有最后一个子串的处理不要忘记,由于当to
遍历到字符串末尾时,会结束循环,最后一个子串将不会在循环内处理。
让咱们提交一下:
击败了73%
的用户,还不错。
想一想看,还有没有优化的空间呢?
那确定是有的,首先咱们想想,当咱们找到的最大子串长度已经比from
所在位置到字符串末尾的位置还要长了,那就没有必要再继续下去了。
class Solution { public int lengthOfLongestSubstring(String s) { if (s == null || s.length() == 0) return 0; int from = 0, to = 1, length = 1, maxLength = 0; // to遍历直到字符串末尾 while (to < s.length()){ int site = s.substring(from, to).indexOf(s.charAt(to)); if (site != -1){ // to指向的字符已存在 length = to - from; if (length > maxLength) { maxLength = length; } // 判断是否须要继续遍历 if (maxLength > s.length() - from + 1) return maxLength; from = from + site + 1; } to++; } // 处理最后一个子串 if (to - from > maxLength) { maxLength = to - from; } return maxLength; } }
另外要处理相似bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
这样的字符串,上面的方法仍是有很大优化空间的,咱们能够用一个HashSet
来存储全部元素,利用其特性进行去重,若是找到的子串长度已经等于HashSet
中的元素个数了,那就不用再继续查找了。
class Solution { public int lengthOfLongestSubstring(String s) { if (s == null || s.length() == 0) return 0; int from = 0, to = 1, length = 1, maxLength = 0; Set<Character> set = new HashSet<>(); for (int i = 0; i < s.length(); i++){ set.add(s.charAt(i)); } // to遍历直到字符串末尾 while (to < s.length()){ int site = s.substring(from, to).indexOf(s.charAt(to)); if (site != -1){ // to指向的字符已存在 length = to - from; if (length > maxLength) { maxLength = length; } if (maxLength > s.length() - from + 1) return maxLength; if (maxLength >= set.size()) return maxLength; from = from + site + 1; } to++; } // 处理最后一个子串 if (to - from > maxLength) { maxLength = to - from; } return maxLength; } }
再提交一下:
哈哈哈哈,翻车了,因此这里引入一个HashSet
用空间来换时间的方式不必定合适,看来测试用例里像bbbbbbbbbbbbbb
这样的用例并很少啊。
那么今天的翻车就到此为止了,若是以为对你有帮助的话记得点个关注哦。