/**
* 给定⼀个字符串,找到没有重复字符的最⻓⼦串,返回它的⻓度
*
* 'pwwkew' answer is 'wke' 3
* nnn is n 1
* abcabcbb is abc 3
*/
public class Test03 {
public static void main(String[] args) {
// System.out.println(lengthOfLongestSubstring("pwwkewq"));
// System.out.println(lengthOfLongestSubstring("abcabcbb"));
// System.out.println(lengthOfLongestSubstring02("pwwkewq"));
// System.out.println(lengthOfLongestSubstring03("pwwkewq"));
System.out.println(lengthOfLongestSubstring04("pwwkewq"));
}
/**
* 解法一: 2个指针移动 截取范围内的字符串(双重 for)
* 先固定一指针, 内层移动另一指针, 两指针中的字符串每位字符放在Set中,如果有重复,不用比较,指针向右移动
* 判断 字符串是否重复, 不重复
* 和当前保存的字串长度比较, 若更大, 更新变量值
*
* 时间复杂度:两个循环,加上判断⼦串满⾜不满⾜条件的函数中的循环,O(n³)。
*
* 空间复杂度:使⽤了⼀个 set,判断⼦串中有没有重复的字符。由于 set 中没有重复的字符,所以最⻓就是
* 整个字符集,假设字符集的⼤⼩为 m ,那么 set 最⻓就是 m 。另⼀⽅⾯,如果字符串的⻓度⼩于 m ,是
* n 。那么 set 最⻓也就是 n 了。综上,空间复杂度为 O(min(m,n))
* @param s
* @return
*/
static int lengthOfLongestSubstring(String s) {
int ans =0;//保存当前得到满⾜条件的⼦串的最⼤值;
int n = s.length(); //字符串长度
for (int i = 0; i < n; i++) {
for (int j = i + 1; j <= n; j++) {//之所以 j<= n,是因为我们⼦串是 [i,j),左闭右开
if (allUnique(s, i, j)) {
ans = Math.max(ans, j - i); //比较当前保存的字串长度,并重新赋值更新 ans
}
}
}
return ans;
}
private static boolean allUnique(String s, int start, int end) {
Set<Character> set = new HashSet<>();//初始化 hash set
for (int i = start; i < end; i++) {//遍历每个字符
Character ch = s.charAt(i);
if (set.contains(ch)){ return false;} //判断字符在不在 set 中
set.add(ch);//不在的话将该字符添加到 set ⾥边
}
return true;
}
/**
* 其实整个关于 j 的循环我们完全可以去掉了,此时可以理解变成了⼀个「滑动窗⼝」。
* 整体就是橘⾊窗⼝在依次向右移动。
* 做了很多重复的⼯作,因为如果 str[0,3) 中没有重复的字符,我们不需要再判断整个字符串 str[0,4) 中有没
* 有重复的字符,⽽只需要判断 str[3] 在不在 str[0,3) 中,不在的话,就表明 str[0,4) 中没有重复的字符。
* 如果在的话,那么 str[0,5) ,str[0,6) ,str[0,7) ⼀定有重复的字符,所以此时后边的 j 也不需要继续增加
* 了。i ++ 进⼊下次的循环就可以了。
*
* 判断⼀个字符在不在字符串中,我们需要可以遍历整个字符串,遍历需要的时间复杂度就是 O(n),加上
* 最外层的 i 的循环,总体复杂度就是 O(n²)。我们可以继续优化,判断字符在不在⼀个字符串,我们可以
* 将已有的字符串存到 Hash ⾥,这样的时间复杂度是 O(1),总的时间复杂度就变成了 O(n)。
*
* 时间复杂度:在最坏的情况下,while 循环中的语句会执⾏ 2n 次,例如 abcdefgg,开始的时候 j ⼀直后移
* 直到到达第⼆个 g 的时候固定不变 ,然后 i 开始⼀直后移直到 n ,所以总共执⾏了 2n 次,时间复杂度为 O
* (n)。
* 空间复杂度:和上边的类似,需要⼀个 Hash 保存⼦串,所以是 O(min(m,n))
*/
static int lengthOfLongestSubstring02(String s) {
int n = s.length(); //pwwkew
Set<Character> set = new HashSet<>();
int ans = 0, i = 0, j = 0;
while (i < n && j < n) {
if (!set.contains(s.charAt(j))) {
set.add(s.charAt(j++));
ans = Math.max(ans, j - i);
} else {
set.remove(s.charAt(i++));
}
}
return ans;
}
/**
* 解法3: i 直接跳转到出现重复字符的下一位
* 所以需要用 map 记录字符及对应的下标
* @param s
* @return
*/
static int lengthOfLongestSubstring03(String s) {
int n = s.length(), ans = 0;
Map<Character, Integer> map = new HashMap<>();
for (int i = 0, j = 0; j< n; j++) {
if(map.containsKey(s.charAt(j))) {
i = Math.max(i, map.get(s.charAt(j)));
}
ans = Math.max(ans, j-i +1);
map.put(s.charAt(j), j + 1);//下标 + 1 代表 i 要移动的下个位置
}
return ans;
}
/**
* 和解法三思路⼀样,区别的地⽅在于,我们不⽤ Hash ,⽽是直接⽤数组,字符的 ASCII 码值作为数组的下
* 标,数组存储该字符所在字符串的位置。适⽤于字符集⽐较⼩的情况,因为我们会直接开辟和字符集等⼤的
* 数组。
* 和解法 3 不同的地⽅在于
* @param s
* @return
*/
static int lengthOfLongestSubstring04(String s) {
int n = s.length(), ans = 0;
int[] index = new int[128];
for (int j = 0, i = 0; j < n; j++) {
i = Math.max(index[s.charAt(j)], i);
ans = Math.max(ans, j - i + 1);
index[s.charAt(j)] = j + 1;//(下标 + 1) 代表 i 要移动的下个位置
}
return ans;
}
}
LeetCode-03无重复字符的最长子串(多种解法)
于 2023-06-14 21:28:50 首次发布