← Назад к вопросам
Поиск максимальной подстроки без повторяющихся символов
1.0 Junior🔥 91 комментариев
#Другое#Основы Java
Условие
Найдите длину самой длинной подстроки без повторяющихся символов.
Примеры
- "abcabcbb" → 3 ("abc")
- "bbbbb" → 1 ("b")
- "pwwkew" → 3 ("wke")
- "" → 0
Требования
- Используйте технику sliding window (скользящее окно)
- Временная сложность O(n)
- Используйте HashSet или HashMap для отслеживания символов
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Поиск максимальной подстроки без повторяющихся символов
Это классическая задача на применение техники Sliding Window (скользящее окно). Алгоритм позволяет эффективно найти требуемую подстроку за линейное время O(n).
Алгоритм: Sliding Window
Идея очень простая:
- Используем два указателя:
leftиright, которые образуют «окно» - Расширяем окно, добавляя символы справа
- Если находим повтор, сужаем окно слева
- Отслеживаем максимальную длину
Строка: "abcabcbb"
Шаг 1: окно [a] → max = 1
Шаг 2: окно [ab] → max = 2
Шаг 3: окно [abc] → max = 3
Шаг 4: встречаем 'a' (повтор) → удаляем слева [bca]
Шаг 5: окно [bcab] → видим 'b' повторяется → [cab]
Шаг 6: окно [cabc] → видим 'c' повторяется → [abc]
...
Решение с HashSet
public class LongestSubstring {
public static int lengthOfLongestSubstring(String s) {
if (s == null || s.isEmpty()) {
return 0;
}
Set<Character> charSet = new HashSet<>();
int left = 0;
int maxLength = 0;
// right указатель проходит по всей строке
for (int right = 0; right < s.length(); right++) {
char rightChar = s.charAt(right);
// Если символ повторяется, удаляем символы слева
while (charSet.contains(rightChar)) {
char leftChar = s.charAt(left);
charSet.remove(leftChar);
left++;
}
// Добавляем текущий символ
charSet.add(rightChar);
// Обновляем максимальную длину
maxLength = Math.max(maxLength, right - left + 1);
}
return maxLength;
}
public static void main(String[] args) {
System.out.println(lengthOfLongestSubstring("abcabcbb")); // 3
System.out.println(lengthOfLongestSubstring("bbbbb")); // 1
System.out.println(lengthOfLongestSubstring("pwwkew")); // 3
System.out.println(lengthOfLongestSubstring("")); // 0
System.out.println(lengthOfLongestSubstring("au")); // 2
System.out.println(lengthOfLongestSubstring("dvdf")); // 3 ("vdf")
}
}
Пошаговый пример: "abcabcbb"
┌─────────────────────────────────────────┐
│ Строка: a b c a b c b b │
│ Индекс: 0 1 2 3 4 5 6 7 │
└─────────────────────────────────────────┘
Итерация 1: right=0 ('a')
charSet = {a}, left = 0
length = 0 - 0 + 1 = 1, maxLength = 1
Итерация 2: right=1 ('b')
charSet = {a, b}, left = 0
length = 1 - 0 + 1 = 2, maxLength = 2
Итерация 3: right=2 ('c')
charSet = {a, b, c}, left = 0
length = 2 - 0 + 1 = 3, maxLength = 3 ✓
Итерация 4: right=3 ('a') — повтор!
while loop:
- удаляем 'a' (left=0)
- left = 1
charSet = {b, c, a}, left = 1
length = 3 - 1 + 1 = 3, maxLength = 3
Итерация 5: right=4 ('b') — повтор!
while loop:
- удаляем 'b' (left=1)
- left = 2
charSet = {c, a, b}, left = 2
length = 4 - 2 + 1 = 3, maxLength = 3
Итерация 6: right=5 ('c') — повтор!
while loop:
- удаляем 'c' (left=2)
- left = 3
charSet = {a, b, c}, left = 3
length = 5 - 3 + 1 = 3, maxLength = 3
Итерация 7: right=6 ('b') — повтор!
while loop:
- удаляем 'a' (left=3)
- left = 4
- 'b' всё ещё в set
- удаляем 'b' (left=4)
- left = 5
charSet = {c, b}, left = 5
length = 6 - 5 + 1 = 2, maxLength = 3
Итерация 8: right=7 ('b') — повтор!
while loop:
- удаляем 'c' (left=5)
- left = 6
charSet = {b}, left = 6
length = 7 - 6 + 1 = 2, maxLength = 3
✅ Результат: maxLength = 3
Решение с HashMap (для отслеживания позиций)
Этот вариант немного более эффективен, так как можно сразу переместить left указатель:
public class LongestSubstringOptimized {
public static int lengthOfLongestSubstring(String s) {
if (s == null || s.isEmpty()) {
return 0;
}
Map<Character, Integer> charIndex = new HashMap<>();
int left = 0;
int maxLength = 0;
for (int right = 0; right < s.length(); right++) {
char rightChar = s.charAt(right);
// Если символ есть в окне, переместим левый указатель
if (charIndex.containsKey(rightChar)) {
// Перемещаемся дальше последнего вхождения этого символа
left = Math.max(left, charIndex.get(rightChar) + 1);
}
// Сохраняем позицию символа
charIndex.put(rightChar, right);
// Обновляем максимальную длину
maxLength = Math.max(maxLength, right - left + 1);
}
return maxLength;
}
}
Преимущество: Не нужен while loop, а значит несколько быстрее (хотя сложность одинакова).
Анализ сложности
Временная сложность: O(n)
- Каждый символ посещается ровно один раз указателем right
- Каждый символ удаляется максимум один раз
- Общее количество операций: 2n
Пространственная сложность: O(min(n, m))
n— длина строкиm— размер алфавита (для ASCII: 128, для Unicode: до 1 млн)- HashSet/HashMap содержит максимум уникальные символы в текущем окне
Почему Sliding Window работает?
- Монотонность: Если подстрока
[left, right]содержит повтор, то любая подстрока[left, right+k]тоже содержит повтор - Оптимальность: Когда мы находим повтор, нет смысла проверять окна с тем же левым указателем
- Правильность: Мы гарантированно проверяем все возможные окна
Альтернативное решение: Brute Force (неэффективно)
// ❌ O(n²) или O(n³) — медленно
public static int lengthOfLongestSubstringBruteForce(String s) {
int maxLength = 0;
// Проверяем все подстроки
for (int i = 0; i < s.length(); i++) {
Set<Character> charSet = new HashSet<>();
for (int j = i; j < s.length(); j++) {
if (charSet.contains(s.charAt(j))) {
break;
}
charSet.add(s.charAt(j));
maxLength = Math.max(maxLength, j - i + 1);
}
}
return maxLength;
}
Тестовые случаи
System.out.println(lengthOfLongestSubstring("abcabcbb")); // 3 ("abc")
System.out.println(lengthOfLongestSubstring("bbbbb")); // 1 ("b")
System.out.println(lengthOfLongestSubstring("pwwkew")); // 3 ("wke")
System.out.println(lengthOfLongestSubstring("")); // 0
System.out.println(lengthOfLongestSubstring("a")); // 1 ("a")
System.out.println(lengthOfLongestSubstring("au")); // 2 ("au")
System.out.println(lengthOfLongestSubstring("dvdf")); // 3 ("vdf")
System.out.println(lengthOfLongestSubstring("abcdefghijklmnopqrstuvwxyz")); // 26
Рекомендация
Используйте HashMap версию если нужна максимальная производительность, или HashSet версию если нужна простота и читаемость. Обе работают за O(n)!