题目:无重复字符的最长子串
给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。
输入: s = "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
方法一:自己想出来的
自己想出来的思路,消耗内存和执行用时都不怎么乐观。应该还可以优化,有时间再修改。
思路:
把第一个元素设为子串,和字符串的下一位比较,子串中没有相同元素则把该元素添加到子串中。如果有相同元素,记录当前子串的长度,则把子串中从头直至相同元素排出,再次比较子串是否有和下一位相同的元素。直至整个字符串都添加过子串中。最大的字串长度则是符合题意的最长字串的长度。可能描述的不清楚,可以看下面的示例
例如:字符串abcabcbb 括号表示子串
(a)bcabcbb b和子串(a)里没有相同元素,把b添加到子串中
(ab)cabcbb c和子串(ab)里没有相同元素,把c添加到子串中
(abc)abcbb a和子串(abc)里相同元素,当前子串长度为3,把子串里的直至相同元素排出去,并把相同元素添加进子串。现子串(bca)
a(bca)bcbb b和子串(bca)里相同元素,当前子串长度为3,把子串里的直至相同元素排出去,并把相同元素添加进子串。现子串(cab)
ab(cab)cbb c和子串(cab)里相同元素,当前子串长度为3,把子串里的直至相同元素排出去,并把相同元素添加进子串。现子串(abc)
abc(abc)bb b和子串(abc)里相同元素,当前子串长度为3,把子串里的直至相同元素排出去,并把相同元素添加进子串。现子串(cb)
abcab(cb)b b和子串(cb)里相同元素,当前子串长度为2,把子串里的直至相同元素排出去,并把相同元素添加进子串。现子串(b)
abcabcb(b) 当前字串长度为1,循环结束。
所以最长字串长度为3
public static int lengthOfLongestSubstring(String s) {
if (s.length() <= 0 || s == "" || s == null) {
return 0;
}
//下标
int index= 0;
//记录当前子串的长度
int a = 1;
//记录最大长度
int ans=0;
for (int i = 0; i < s.length() -1; i++) {
String str = s.substring(index, i + 1);
//子串和下一位不相等
if (str.indexOf(s.charAt(i + 1)) == -1) {
a ++;
}else{
//排除子串中直至相同元素后,新子串第一位的索引
index =str.indexOf(s.charAt(i + 1))+ 1 +index;
//记录最大的长度
if (a> ans) ans = a;
//当前子串的长度,+1是把相同元素添加进子串后计算长度
a =s.substring(index,i+1).length()+1;
}
}
if (a> ans) ans = a;
return ans;
}
消耗内存和执行用时多,应该还可以优化(减少循环?),有时间再弄。
方法二:官方给的方法(滑动窗口)
找出每个字符开始的,不包含重复元素的最长字串
设置一个HashSet判断是否有重复元素,没有重复元素就一直往里面添加。遇到重复元素移除HashSet里第一个元素,如果依旧重复就继续移除,直至不重复就继续添加。每次添加记录HashSet长度,最大的长度就是本题所求。
public static int lengthOfLongestSubstring(String s) {
Set<Character> hashSet = new HashSet<>();
//右指针,不断添加
int right = -1;
int ans = 0;
//i可以看作左指针,从左边移除
for (int i = 0; i < s.length(); i++) {
//i为0时HashSet为空不用移除
if (i != 0) {
hashSet.remove(s.charAt(i - 1));
}
//字符串的下一位不与HashSet里面元素相同,添加进hashSet,右指针向右移动。
//遇到相同元素,结束本次小循环,进大循环(移除下标i的元素,左指针+1(i+1)。
//即HashSet集合中第一位元素变成下标为i+1的元素
while (right + 1 < s.length() && !hashSet.contains(s.charAt(right + 1))) {
hashSet.add(s.charAt(right + 1));
right++;
}
//遇到有重复元素,则记录最大长度
ans = Math.max(ans, right - i + 1);
}
return ans;
}
示例:字符串abcabcbb ()里为HashSet集合里面的元素。
(a)bcabcbb (ab)cabcbb (abc)abcbb i=0时,不断添加,当right=2时,charAt(right+1) 为HashSet里重复元素。
a(bc)abcbb a(bca)bcbb i=1时,移除重复元素charAt(i-1),再次不断添加。当right=3时,charAt(right+1) 为HashSet里重复元素。
ab(ca)bcbb ab(cab)cbb i=2时
abc(ab)cbb abc(abc)bb i=3时
abca(bc)bb i=4时,hashSet里元素依旧存在下一位元素charAt(right+1) right为5 相同,结束本次小循环,开始新一轮大循环。
abcab(c)bb abcab(cb)b i=5时
abcabc(b)b i=6时
abcabcb()b abcabcb(b) i=7时
方法三:暴力求解
记录所有不重复子串的长度,找到最大的长度即为题目所求。
public static int lengthOfLongestSubstring(String s) {
int n = s.length();
int ans = 0;
Set<Character> set = new HashSet<>();
for (int i = 0; i < n; i++) {
for (int j = i + 1; j <= n; j++) {
String str = s.substring(i, j);
char[] chars = str.toCharArray();
for (char c : chars) {
set.add(c);
}
//相等意味着str没有重复元素
if (set.size() == str.length()) {
ans = Math.max(ans, str.length());
}else{
//已经存在重复元素,j再怎么变化依旧存在重复元素,直接结束这个循环
set.clear();
break;
}
}
}
return ans;
}
HashSet不存放重复元素,所有元素存放到HashSet中,如果HashSet长度和字符串长度不相同,就意味着有重复元素(HashSet没存放那个重复元素,所以导致长度不同)。
方法四:评论区 画解算法的方法 改进版
map集合,存放字符和字符最后一次出现的位置。
每次出现相同元素就把start移动到 start和end中相同元素出现的 下一位
public static int lengthOfLongestSubstring(String s){
int n = s.length();
int ans= 0;
HashMap<Character, Integer> map = new HashMap<>();
for (int start = 0,end = 0; end<n;end++){
char c = s.charAt(end);
if(map.containsKey(c)){
//相同字符出现位置的下一位,start不能比当前的start小
start = Math.max(map.get(c)+1,start);
}
map.put(c,s.indexOf(c));
ans=Math.max(ans,end-start+1);
}
return ans;
}
start = Math.max(map.get(c)+1,start)语句 的作用
例如字符串 pwpwewp,最后一次循环start再‘e'位置,end再‘p’位置。如果没有这句start将回到‘p’上一次出现位置的下一位,即索引3。我们可以看到子串wewp不符合条件。预防这种情况,start左移动,所以要添加这一句。
方法五:评论区看到的一种方法
public static int lengthOfLongestSubstring(String s){
int maxSize = 0;
//记录ASCII 码字符出现的位置,以字符作为下标
int[] dict = new int[128];
//为了方便理解,这里把数组内容全部设为 -1,之后在记录的时候就可以从 0 开始,方便理解
Arrays.fill(dict, -1);
//用于记录重复 ASCII 码字符出现的位置的值
int repeatValue = -1;
// 当前下标
int i = 0;
//存放左下标
int ASCII;
while (i < s.length()) {
ASCII = s.charAt(i);
//如果当前位置的值 > repeatValue,证明当前位置已经赋过一次值了,证明字符重复
if (dict[ASCII] > repeatValue)
//更新 repeatValue 为之前赋值的下标
repeatValue = dict[ASCII];
//将当前下标赋值到数组相应位置
dict[ASCII] = i;
//i - repeatValue(去除重复部分)
// 比如 abcabcdade 中的三个 a 的计算 abca - a(3 - 0)=bca abcabcda - abca(7 - 3)=bcda
maxSize = Math.max(maxSize, i - repeatValue);
//s.length() - repeatValue - 1 判断剩下的数有没有必要继续循环
//比如 abcabcdade 最后的 a(当 i = 7 repeatValue = 3) ,abcabcdade - abca(10-3-1) = bcdade 剩下最多有六位
//比如 abcabcdade 最后的 d(当 i = 8 repeatValue = 6) ,abcabcdade - abcabcd(10-6-1) = ade 剩下最多也是三位
if (maxSize >= s.length() - repeatValue - 1) {
return maxSize;
}
i++;
}
return maxSize;
}
和第四种方法差不多,用数组记录最后出现一次的下标。int[128],大多数字符都在ASCII前。128足够用了
学习到的东西:
set也可以判断字符串是否含有重复元素。相比两次循环每个元素都比较更优。
滑动窗口:左指针固定,右指针不断右移。不满足条件时右指针固定,左指针右移动一位。