给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。html
示例 1:数据结构
输入: "abcabcbb"函数
输出: 3优化
解释: 由于无重复字符的最长子串是
"abc",因此其
长度为 3。spa
示例 2:code
输入: "bbbbb"htm
输出: 1blog
解释: 由于无重复字符的最长子串是
"b"
,因此其长度为 1。索引
示例 3:队列
输入: "pwwkew"
输出: 3
解释: 由于无重复字符的最长子串是 "wke",因此其长度为 3。
请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。子串:串中任意个连续的字符组成的子序列称为该串的子串
题目更新后因为时间限制,会出现 TLE(超时)。
逐个检查全部的子字符串,看它是否不含有重复的字符。
假设咱们有一个函数 boolean allUnique(String substring) ,若是子字符串中的字符都是惟一的,它会返回 true,不然会返回 false。 咱们能够遍历给定字符串 s 的全部可能的子字符串并调用函数 allUnique。
若是事实证实返回值为 true,那么咱们将会更新无重复字符子串的最大长度的答案。
如今让咱们填补缺乏的部分:
1.为了枚举给定字符串的全部子字符串,咱们须要枚举它们开始和结束的索引。
2.要检查一个字符串是否有重复字符,咱们可使用集合。咱们遍历字符串中的全部字符,并将它们逐个放入set中。
在放置一个字符以前,咱们检查该集合是否已经包含它。若是包含,咱们会返回false。循环结束后,咱们返回true。
class Solution { public int lengthOfLongestSubstring(String s) { //字符串长度
int n = s.length(); //值为最长子串的长度
int ans = 0; for (int i = 0; i < n; i++) { for (int j = i + 1; j <= n; j++) { if (allUnique(s, i, j)) { //更新ans的值,取最大
ans = Math.max(ans, j - i); } } } return ans; } /** * 求最长无重复字符的最长子串 * * @param s 字符串长度 * @param start 字符串当前开始位置 * @param end 字符串当前结束位置 * @return ture或false,决定要不要进入上面的if循环 */
public boolean allUnique(String s, int start, int end) { Set<Character> set = new HashSet<>(); for (int i = start; i < end; i++) { //第i个字符在字符串中内容
Character ch = s.charAt(i); //Set集合是否包含字符串ch
if (set.contains(ch)) { return false; } //若是不包含,就往set集合里添加ch字符串
set.add(ch); } return true; } }
时间复杂度:O (n3)
其实就是一个队列,好比例题中的abcabcbb,进入这个队列(窗口)为abc 知足题目要求,当再进入a,队列变成了abca,这时候不知足要求。因此,咱们要移动这个队列!
如何移动?
咱们只要把队列的左边的元素移出就好了,直到知足题目要求!一直维持这样的队列,找出队列出现最长的长度时候,求出解!
固然滑动窗口的尺寸能够是固定也能够是动态的
经过使用 HashSet 做为滑动窗口,咱们能够用 O(1)O(1) 的时间来完成对字符是否在当前的子字符串中的检查。
回到咱们的问题,咱们使用HashSet将字符存储在当前窗口[i,,j)(最初=i)中。而后咱们向右侧滑动索引j,若是它不在HashSet中,咱们会继续滑动。直到si]已经存在于HashSet中。此时,咱们找到的没有重复字符的最长子字符串将会以索引i开头。若是咱们对全部的i这样作,就能够获得答案。
public class Solution { public int lengthOfLongestSubstring(String s) { //字符串长度
int n = s.length(); Set<Character> set = new HashSet<>(); //定义ans为最长子串的长度,若是j的值没有在set出现过end++,知道出现start++,滑动窗口
int ans = 0, start = 0, end = 0; //若是start和end<n就一直遍历,直到start和end都小于n
while (start < n && end < n) { //判断Set集合是否包含end下标对应的字符串
if (!set.contains(s.charAt(end))){ //若是set里没有,则把j下标对应的字符串存进set集合, end执行完本句后+1
set.add(s.charAt(end++)); //由于end++,全部滑动窗口向后移动,并更新ans的值
ans = Math.max(ans, end - start); } else { //移除Set集合start下标对应的字符串,start++,最后所有移除
set.remove(s.charAt(start++)); } } return ans; } }
时间复杂度:O(2n)=O(n),在最糟糕的状况下,每一个字符将被i和j访问两次。
空间复杂度:O(min(m,n)),与以前的方法相同。滑动窗口法须要O(k)的空间,其中k表示Set的大小。而Set的大小取决于字符串n的大小以及字符集/字母m的大小。
上述的方法最多须要执行2n个步骤。事实上,它能够被进一步优化为仅须要n个步骤。咱们能够定义字符到索引的映射,而不是使用集合来判断一个字符是否存在。当咱们找到重复的字符时,咱们能够当即跳过该窗口。
也就是说,若是s[j]在[i,j)范围内有与j重复的字符,咱们不须要逐渐增长i。咱们能够直接跳过[i,j门范围内的全部元素,并将i变为j+1。
public class Solution { public int lengthOfLongestSubstring(String s) { int n = s.length(), ans = 0; //建立map窗口
Map<Character, Integer> map = new HashMap<>(); //定义不重复子串的开始位置为 start,结束位置为 end
for (int end = 0, start = 0; end < n; end++) { if (map.containsKey(s.charAt(end))) { // 随着 end 不断遍历向后,会遇到与 [start, end] 区间内字符相同的状况, // map.get(s.charAt(end)此时将字符做为 key 值,获取其 value 值, // 此时 [start, end] 区间内不存在重复字符。 // 修改start值为以前重复字符位置以后的位置
// start更新时进行比较是由于map里key放的是全部遍历过的字符,而不只仅是窗口里的字符。
// 因此如今的字符若是跟窗口外的字符(也就是之前的字符)重复了,start应该保持原样
例如 abcdefcma 到第二个c的时候 i变成了 3,但到第二个 a的时候若是取第一个a的位置,而不是a和当前i的最大值, i就变成了1,左侧索引左移
start = Math.max(map.get(s.charAt(end)), start); } //比对当前无重复字段长度和储存的长度,选最大值并替换 //end-start+1是由于此时i,end索引仍处于不重复的位置,end尚未向后移动,取的[start,end]长度,+1由于索引从1开始 //不管是否更新 start,都会更新其 map 数据结构和结果 ans。
ans = Math.max(ans, end - start + 1); // 将当前字符为key,value 值为字符位置 +1 // 加 1 表示从字符位置后一个才开始不重复,否则就把最后一个数给替换了
map.put(s.charAt(end), end + 1); } return ans; } }
时间复杂度:O(n),索引j将会迭代n次。
空间复杂度(HashMap):O(min(m,n)),与以前的方法相同。
空间复杂度(Table):O(m),m是字符集的大小。