第一次本地尝试
思路
白纸代码
IDE 代码
import java.util.HashSet;
public class MaxUnrptSubStr_Wrong1 {
public static int lengthOfLongestSubstring(String s) {
// 辅助变量
int tmpLength = 0, maxLength = 0;
char[] strChars = s.toCharArray();
// 默认大小与充填因子的HashSet
HashSet charSet = new HashSet();
// 遍历strChars
for (char c : strChars) {
charSet.add(c);
++tmpLength;
if (charSet.size() != tmpLength) {
maxLength = maxLength < --tmpLength ? tmpLength : maxLength;
charSet.clear();
charSet.add(c);
tmpLength = 0;
}
}
return maxLength;
}
public static void main(String[] args) {
String s = "asdddqwdfsdd";
System.out.println(MaxUnrptSubStr_Wrong1.lengthOfLongestSubstring(s));
}
}
时间空间复杂度
时间复杂度:HashSet要去重,时间复杂度未知,但是应该是小于O(n),所以该解法的时间复杂度小于O( n2 n 2 )【TODO:HashSet的时间复杂度是多少】
空间复杂度:两个辅助变量,是O(1); 但是set,O(n)
所以,空间复杂度,O(n )
结果不对的原因及串
所清掉的字符中,有的字符是可以算入下一次子串长度的,比如:
“ad d qwdfs d d“,结果应该是5,但是以上代码会返回3
- 第一次记录为ad,然后遇见第二个d,清除,这个清除是没有问题的,因为两个d是挨着的;
- 第二次记录为dqw,然后遇见第二个d,清除,这个清除就有问题了,因为qw对于下一子串是有意义的,这也是最终的返回结果,3
- 而正确答案应该是5,qwdfs
- 遇到重复值时,不应该清除set中所有的元素,仅清除它以及它前面的即可
~结论:不用Set,Set无状态无序~
第二次本地尝试
思路
- 贪心算法?不满足,无法拆分为子结构,也没有“贪“的策略。
- 分治法?不满足,同样没办法分子问题啊,分了也没办法独立。
- 回溯法?动态规划?我还只知道概念。。。
- 暴力求解?不如不做
白纸代码
最终Ac代码
public class MaxUnrptSubStr {
/**
* 解空间[0,26],25为下标最大增加数
*/
private static int MAX = 25;
public int lengthOfLongestSubstring(String s) {
int index = -1, maxLength = 0;
char[] strChars = s.toCharArray();
if (strChars.length== 0 | strChars.length== 1) {return strLength;}
HashSet charSet = new HashSet();
for (char c : strChars) {
++index;
charSet.add(c);
for (int i = 0; i < Math.min(strLength - index, MAX); i++) {
charSet.add(strChars[index + i]);
if (charSet.size() <= i) {
maxLength = maxLength < charSet.size() ? charSet.size(): maxLength;
charSet.clear();
break;
}
}
maxLength = maxLength < charSet.size() ? charSet.size() : maxLength;
}
return maxLength;
}
}
反思
不缜密/不好的地方:
- 空字符串;
- 第二个for中的if,不匹配if时,忘记了给maxLength赋值;
- 时间复杂度仍然是O( n2 n 2 ),与暴力的情况唯一不同的是,判断是否重复不是自己一一匹配的,时间复杂度应该可以低于O( n2 n 2 )
结果也是很惨的,仅好于23.21%的人
借鉴他人解决方案后的改进思路
- 作者是“双指针”的思想,使用HashSet的“叔叔”HashMap,该map中,“脑袋”指针一直往字符串末尾走,“尾巴”指针则停留在上一次重复的字符的下标+1的位置。【就如我的第一次尝试,仅需要删除重复字符以及其前面的字符就可】
- 利用了HashMap相同key默认去重的特点,key是字符,value是下标,用下标减去“尾巴”更新maxLength的值
- 我做的修改:没有什么实质的修改。。。把变量名改得更容易懂一些。。。
借鉴改进后最终代码
public int lengthOfLongestSubstring(String s) {
int strLength = s.length();
if (strLength == 0 | strLength == 1) {
return strLength;
}
HashMap<Character, Integer> map = new HashMap<>();
int maxLength = 0;
for (int globalIndex = 0, newSubStrBegin = 0; globalIndex < s.length(); ++globalIndex) {
if (map.containsKey(s.charAt(globalIndex))) {
newSubStrBegin = Math.max(newSubStrBegin, map.get(s.charAt(globalIndex)) + 1);
}
// 重复的仍然put,对于key,hashmap的onlyIfAbsent参数默认为false,会替换相同key之前的value:putVal(hash(key), key, value, false, true);
map.put(s.charAt(globalIndex), globalIndex);
maxLength = Math.max(maxLength, globalIndex - newSubStrBegin + 1);
}
return maxLength;
}
简单解释
时间及空间复杂度
- 时间复杂度
作者认为是O(n),他显然不计入hashmap判断containskey以及hashmap更新value的时间,
时间不只O(n),比O( n2 n 2 )低一些 - 空间复杂度
O(n) - 结果优于54%的人,仍然有很大提升空间。
TODO
- HashSet的时间复杂度是多少