← Назад к вопросам
Самая длинная подстрока без повторяющихся символов
1.8 Middle🔥 151 комментариев
#Основы Go
Условие
Дана строка s. Найдите длину самой длинной подстроки без повторяющихся символов.
Сигнатура
func lengthOfLongestSubstring(s string) int
Примеры
Вход: "abcabcbb"
Выход: 3 (подстрока "abc")
Вход: "bbbbb"
Выход: 1 (подстрока "b")
Вход: "pwwkew"
Выход: 3 (подстрока "wke")
Подсказка
Используйте технику скользящего окна (sliding window).
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Решение: Самая длинная подстрока без повторяющихся символов
Основная идея: Sliding Window
Используем два указателя (левый и правый):
- Правый указатель движется вперёд, добавляя символы
- Если встретили повторение, движем левый указатель
- Отслеживаем максимальную длину
Оптимальная реализация O(n)
func lengthOfLongestSubstring(s string) int {
// Map для хранения последнего индекса каждого символа
charIndex := make(map[rune]int)
maxLength := 0
left := 0 // Левый указатель окна
for right := 0; right < len(s); right++ {
char := rune(s[right])
// Если символ повторяется и находится в текущем окне
if idx, exists := charIndex[char]; exists && idx >= left {
// Передвигаем левый указатель за предыдущее вхождение
left = idx + 1
}
// Запоминаем индекс текущего символа
charIndex[char] = right
// Обновляем максимальную длину
currentLength := right - left + 1
if currentLength > maxLength {
maxLength = currentLength
}
}
return maxLength
}
Сложность:
- Время: O(n) — один проход по строке
- Память: O(min(n, m)) — где m это размер алфавита (ASCII: 128, Unicode: ~1M)
Пошаговый пример для "abcabcbb"
Индекс: 0 1 2 3 4 5 6 7
Строка: a b c a b c b b
Шаг 1: right=0, char='a'
charIndex={'a': 0}
left=0, length=1, maxLength=1
Шаг 2: right=1, char='b'
charIndex={'a': 0, 'b': 1}
left=0, length=2, maxLength=2
Шаг 3: right=2, char='c'
charIndex={'a': 0, 'b': 1, 'c': 2}
left=0, length=3, maxLength=3
Шаг 4: right=3, char='a' (ПОВТОРЕНИЕ!)
'a' уже в charIndex с индексом 0 >= left(0)
left = 0 + 1 = 1
charIndex={'a': 3, 'b': 1, 'c': 2}
length = 3-1+1 = 3, maxLength=3
Шаг 5: right=4, char='b' (ПОВТОРЕНИЕ!)
'b' уже в charIndex с индексом 1 >= left(1)
left = 1 + 1 = 2
charIndex={'a': 3, 'b': 4, 'c': 2}
length = 4-2+1 = 3, maxLength=3
Шаг 6: right=5, char='c' (ПОВТОРЕНИЕ!)
'c' уже в charIndex с индексом 2 >= left(2)
left = 2 + 1 = 3
charIndex={'a': 3, 'b': 4, 'c': 5}
length = 5-3+1 = 3, maxLength=3
Шаг 7: right=6, char='b' (ПОВТОРЕНИЕ!)
'b' уже в charIndex с индексом 4 >= left(3)
left = 4 + 1 = 5
charIndex={'a': 3, 'b': 6, 'c': 5}
length = 6-5+1 = 2, maxLength=3
Шаг 8: right=7, char='b' (ПОВТОРЕНИЕ!)
'b' уже в charIndex с индексом 6 >= left(5)
left = 6 + 1 = 7
charIndex={'a': 3, 'b': 7, 'c': 5}
length = 7-7+1 = 1, maxLength=3
Результат: maxLength = 3
Альтернатива: с массивом вместо map (для ASCII)
func lengthOfLongestSubstring(s string) int {
// Массив для ASCII символов (256)
lastIndex := make([]int, 256)
for i := range lastIndex {
lastIndex[i] = -1 // Изначально -1 означает символ не встречался
}
maxLength := 0
left := 0
for right := 0; right < len(s); right++ {
char := s[right]
// Если символ был в текущем окне
if lastIndex[char] >= left {
left = lastIndex[char] + 1
}
lastIndex[char] = right
currentLength := right - left + 1
if currentLength > maxLength {
maxLength = currentLength
}
}
return maxLength
}
Преимущество: Немного быстрее для ASCII (array lookup vs map lookup) Недостаток: Фиксирован размер (256), не работает корректно для всех Unicode
Версия с возвращением самой подстроки
func lengthOfLongestSubstringWithString(s string) (int, string) {
charIndex := make(map[rune]int)
maxLength := 0
maxStart := 0
left := 0
for right := 0; right < len(s); right++ {
char := rune(s[right])
if idx, exists := charIndex[char]; exists && idx >= left {
left = idx + 1
}
charIndex[char] = right
currentLength := right - left + 1
if currentLength > maxLength {
maxLength = currentLength
maxStart = left
}
}
return maxLength, s[maxStart : maxStart+maxLength]
}
Примеры работы
func main() {
fmt.Println(lengthOfLongestSubstring("abcabcbb")) // 3
fmt.Println(lengthOfLongestSubstring("bbbbb")) // 1
fmt.Println(lengthOfLongestSubstring("pwwkew")) // 3
fmt.Println(lengthOfLongestSubstring("")) // 0
fmt.Println(lengthOfLongestSubstring("au")) // 2
fmt.Println(lengthOfLongestSubstring("dvdf")) // 3 (vdf)
}
Версия с Unicode поддержкой
func lengthOfLongestSubstring(s string) int {
// Конвертируем в runes для корректной работы с Unicode
runes := []rune(s)
charIndex := make(map[rune]int)
maxLength := 0
left := 0
for right := 0; right < len(runes); right++ {
char := runes[right]
if idx, exists := charIndex[char]; exists && idx >= left {
left = idx + 1
}
charIndex[char] = right
currentLength := right - left + 1
if currentLength > maxLength {
maxLength = currentLength
}
}
return maxLength
}
Тестирование
func TestLongestSubstring(t *testing.T) {
tests := []struct {
input string
expected int
}{
{"abcabcbb", 3},
{"bbbbb", 1},
{"pwwkew", 3},
{"", 0},
{"au", 2},
{"dvdf", 3},
{"abcdefghijk", 11},
}
for _, test := range tests {
result := lengthOfLongestSubstring(test.input)
if result != test.expected {
t.Errorf("Input: %s, Expected: %d, Got: %d", test.input, test.expected, result)
}
}
}
Ключевые моменты
- Sliding Window: два указателя движутся по строке
- Map для индексов: храним последнее вхождение каждого символа
- Условие idx >= left: проверяем, что символ в текущем окне
- O(n) время: каждый символ посещается ровно дважды (right и left)
- O(min(n, m)) память: максимум все уникальные символы в map
- Нет вложенных циклов: это очень быстро для больших строк
Когда использовать
- Поиск подпоследовательностей с уникальными элементами
- Компрессия данных (поиск повторений)
- Парсинг и валидация токенов
- Анализ текста
Это классическая задача, которая показывает мастерство в работе с алгоритмами и структурами данных.