【题目描述】

给定一个字符串 ​s​ ,请你找出其中不含有重复字符的 最长子串 的长度

https://leetcode.cn/problems/longest-substring-without-repeating-characters/


【示例】

示例1:2022-10-29

输入: s = "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3

示例2: 注意 pwke是子序列非子串

输入: s = "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。

【代码1】

package com.company;
import java.util.*;

class Solution2 {

public int lengthOfLongestSubstring(String s) {
if (s.length() == 0 ) return 0;
Map<Character, Integer> map = new HashMap<>();

//用于记录最大不重复子串的长度
int max = 0;
//滑动窗口左指针
int left = 0;

/**
1、首先,判断当前字符是否包含在map中,如果不包含,将该字符添加到map(字符,字符在数组下标),
此时没有出现重复的字符,左指针不需要变化。此时不重复子串的长度为:i-left+1,与原来的maxLen比较,取最大值;

2、如果当前字符 ch 包含在 map中,此时有2类情况:
1)当前字符包含在当前有效的子段中,如:abca,当我们遍历到第二个a,当前有效最长子段是 abc,我们又遍历到a,
那么此时更新 left 为 map.get(a)+1=1,当前有效子段更新为 bca;
2)当前字符不包含在当前最长有效子段中,如:abba,我们先添加a,b进map,此时left=0,我们再添加b,发现map中包含b,
而且b包含在最长有效子段中,就是1)的情况,我们更新 left=map.get(b)+1=2,此时子段更新为 b,而且map中仍然包含a,map.get(a)=0;
随后,我们遍历到a,发现a包含在map中,且map.get(a)=0,如果我们像1)一样处理,就会发现 left=map.get(a)+1=1,实际上,left此时
应该不变,left始终为2,子段变成 ba才对。

为了处理以上2类情况,我们每次更新left,left=Math.max(left , map.get(ch)+1).
另外,更新left后,不管原来的 s.charAt(i) 是否在最长子段中,我们都要将 s.charAt(i) 的位置更新为当前的i,
因此此时新的 s.charAt(i) 已经进入到 当前最长的子段中!
*/
for (int i = 0; i < s.length(); i++) {
if (map.containsKey(s.charAt(i))){
left = Math.max(left, map.get(s.charAt(i)) + 1);
}
// 如果没有, 则记录当前字符和下标
map.put(s.charAt(i), i);
max = Math.max(max, i - left + 1);
}
System.out.println(s.substring(0, max));
return max;
}
}
public class Test {
public static void main(String[] args) {
String str = "abcabcbb";
new Solution2().lengthOfLongestSubstring(str);
}
}

【代码2】

​滑动窗口1:set集合+双指针​

package com.company;

import java.util.*;

// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class longest_str_without_repeating_characters {
public static void main(String[] args) {
String str = "pwwkew";
lengthOfLongestSubstring2(str);
}
// 方法1: 最坏的情况下两个指针i和j都需要移动n次,也就是需要两次遍历。
private static int lengthOfLongestSubstring2(String str) {
// 输入的字符串要判空
if(str == null || "".equals(str)){
return 0;
}

int len = str.length();
// 统一从左边0开始, 如果不存在则j++,否则i++
int i = 0;
int j = 0;

Set<Character> set = new HashSet<Character>();
int res = 0;

while (i < len && j <len) {
// 如果j指向的元素不在set中,则放入set中,且指针向右移动(j++), 计算窗口大小
if (!set.contains(str.charAt(j))) {
set.add(str.charAt(j));
j++;
res = Math.max(res, j - i);
} else {
// 如果j在元素中, 则删除重复元素, i++
set.remove(str.charAt(i));
i++;
}
}
// System.out.println(set.toString());
return res;
}
}

滑动窗口2:map + 双指针

滑动窗口1基于set+双指针完成了结果,但这个滑动窗口性能不是最好的,最坏的情况下两个指针i和j都需要移动n次,也就是需要两次遍历。

前面用"pwwkew"举例,当j指针遍历到第二个w的时候,出现重复了,于是i指针开始往前走,最后落在第二个w处。如果借助一个map,key是字符,value是字符的下标。当字符重复的时候,我们就找到这个map中这个重复的字符的下标,然后让指针再移动一位即可, 可以直接让i指针跳到第二个w处。

【LeeCode】3.无重复字符的最长子串_子串


package com.company;

import java.util.*;

public class longest_str_without_repeating_characters {
public static void main(String[] args) {
String str = "pwwkew";
lengthOfLongestSubstring3(str);
}

// 方法1: 最坏的情况下两个指针i和j都需要移动n次,也就是需要两次遍历。
private static int lengthOfLongestSubstring3(String str) {
// 输入的字符串要判空
if (str == null || "".equals(str)) {
return 0;
}

Map<Character, Integer> map = new HashMap<>();
int left = 0;
int res = 0;
for (int i = 0; i < str.length(); i++) {
char c = str.charAt(i);
if (map.containsKey(c)) {
// 如果当前遍历的字符在map中,就更新左窗口
// 假设当前字符是‘a',map.get(s.charAt(i))就是上一个’a'的位置
left = Math.max(left, map.get(c) + 1);
}
// 如果不在map中,则记录当前字符和下标
map.put(c, i);
res = Math.max(res, i - left + 1);
}
return res;
}
}


【其他】

​https://blog.51cto.com/u_13682316/5805328​