题目中文:没有重复字符的最长子串
题目难度:Medium
题目内容:
Given a string, find the length of the longest substring without repeating characters.
Examples:
Given "abcabcbb"
, the answer is "abc"
, which the length is 3.
Given "bbbbb"
, the answer is "b"
, with the length of 1.
Given "pwwkew"
, the answer is "wke"
, with the length of 3. Note that the answer must be a substring, "pwke"
is a subsequence and not a substring.
翻译:
给定一个字符串,计算得到最长子字符串的长度,而子串内不能重复字符。
例子:
给定“abcabcbb”,答案是“abc”,长度为3。
给定“bbbbb”,答案是“b”,长度为1。
给定“pwwkew”,答案是“wke”,长度为3。注意,答案必须是子字符串(连续的),“pwke”是子序列,而不是子字符串。
思路:应为需要的是子串(连续),所以双重循环就可以遍历每一个子串,再用一个函数来判断这个子串内部是否有重复,没有则更新最新的长度
我的代码:
1 public int lengthOfLongestSubstring(String s) { 2 int n = s.length(); // size(),length()这类函数不像变量length,这也是要计算的,当调用一次以上,赋值给一个变量减少复杂度 3 int ans = 0; 4 for (int i = 0; i < n; i++) { 5 for (int j = i + 1; j <= n; j++) { 6 if (isUnique(s, i, j)) { 7 ans = Math.max(ans, j - i); // 如果唯一,则更新ans 8 } 9 } 10 } 11 return ans; 12 } 13 14 public static boolean isUnique(String s, int start, int end) { 15 Set<Character> hset = new HashSet<Character>(); 16 for (int i = start; i < end; i++) { 17 hset.add(s.charAt(i)); 18 } 19 return hset.size() == (end - start) ? true : false; // 如果Set的大小没变说明都是唯一的 20 }
982 / 983 test cases passed. Status: Time Limit Exceeded
超时了。。。明显这个算法复杂度为O(n3),确实大了点。。。
编码期间问题:
1、程序的边界问题困扰了很久,一开始isUnique方法我是设置从 i 到 j 都判断,j到n-1结束,结果发现这样子需要加很多边界判断条件,例如输入是“C”就一个,那么 j 从下标1开始直接就越界根本不会进入循环体。又或者全是同一个字符“cccccccccc”,isUnique只会一直false,ans也不会更新。偷看了下参考答案,发现第一个和我几乎一样,仔细一看发现它的判唯一范围是从 i 到 j 的前一个字符, j的范围从 i+1 一直到 n,这样一来就总会进入循环体,并且ans至少更新一次。
答案一:
code略
和我的几乎一样就是判断唯一的方法里它是这么写的:
public boolean allUnique(String s, int start, int end) { Set<Character> set = new HashSet<Character>(); for (int i = start; i < end; i++) { Character ch = s.charAt(i); if (set.contains(ch)) return false; set.add(ch); } return true; }
可能比我的算法复杂度低一点,因为发现重复的时候它可以直接返回,而我那个是全部添加后才能判断重复。记住set、map都有contains功能。
答案二:
1 public static int lengthOfLongestSubstring2(String s) { 2 int n = s.length(), ans = 0; 3 Map<Character, Integer> map = new HashMap<Character, Integer>(); 4 for (int j = 0, i = 0; j < n; j++) { 5 if (map.containsKey(s.charAt(j))) { 6 i = Math.max(map.get(s.charAt(j)), i); 7 } 8 ans = Math.max(ans, j - i + 1); 9 map.put(s.charAt(j), j + 1); 10 } 11 return ans; 12 }
983 / 983 test cases passed. Runtime: 66 ms beats 25.38% 算法复杂度:O(N)
思路:利用map的key-value结构存储值以及对应顺位(下标+1),然后每次指针(j)下移的时候都判断是否已经包含了此字符,如果是则将 i 的值更新,所以每次ans都是取最大的 i 到 j (包括j)的长度。
所以而 i 指向的一直是不包含重复的子串开头,而 j 指向就是结尾。ans的每次更新,保证了ans是最大的长度。
例如 abcdaa
当 j = 4时,发现map内有此时 j 指向的a,所以更新 i 的值,表明之前的a在顺位第 1 个,所以ans就等于新a的位置(j)减去老a的位置(顺位 i -1),所以相当于 j - i +1 = 4。
当 j = 5时,发现map内有此时 j 指向的a,所以更新 i 的值,表明之前的a在顺位第 5 个,所以ans_temp = 5-5+1 = 1,取max ,ans最终等于4.
为什么不直接存下标?
如果存储的是下标,那么最后更新的ans也只能是 j - i (新位置减去老位置),假如此时整个字符串内都没有重复,那么最后的答案就是length - 1 - 0 ,因为只有当有更新的时候才能下标直接相减。
答案三:
1 public int lengthOfLongestSubstring(String s) { 2 int n = s.length(), ans = 0; 3 int[] index = new int[256]; // current index of character 4 // try to extend the range [i, j] 5 for (int j = 0, i = 0; j < n; j++) { 6 i = Math.max(index[s.charAt(j)], i); 7 ans = Math.max(ans, j - i + 1); 8 index[s.charAt(j)] = j + 1; 9 } 10 return ans; 11 }
983 / 983 test cases passed. Runtime: 66 ms beats 83.43% 算法复杂度:O(N)
其实思想和方法二差不多,有点取巧性质。。。因为字符都能用ascall码表示,而ascall码一共就256个,每一个代表一个字符,(因为后128个键盘输入不了,用128也行)所以直接使用一个int数组当哈希表使用,降低了哈希表的寻址时间复杂度。
以后遇见hash表的key为单个字符时,都可以想到用int[256]来替代map.get(*);
原文地址:https://www.cnblogs.com/Xieyang-blog/p/8529870.html