← Назад к вопросам
Сжатие строки (Run-Length Encoding)
1.2 Junior🔥 111 комментариев
#Другое#Основы Java
Условие
Реализуйте алгоритм сжатия строки методом Run-Length Encoding (RLE).
Последовательные одинаковые символы заменяются на символ и количество повторений.
Примеры
- "aabcccccaaa" → "a2b1c5a3"
- "abcd" → "abcd" (если сжатая строка длиннее, вернуть оригинал)
- "aaa" → "a3"
Требования
- Если сжатая строка не короче оригинала, верните оригинал
- Используйте StringBuilder для эффективности
- Временная сложность O(n)
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Сжатие строки методом Run-Length Encoding (RLE)
Run-Length Encoding — это простой, но эффективный алгоритм сжатия данных, который замещает последовательные одинаковые символы на сам символ и количество его повторений.
Алгоритм
- Проверить, будет ли сжатая строка меньше оригинала
- Пройти по строке и считать последовательные одинаковые символы
- Добавить в результат символ и его количество
- Если итоговая длина >= оригинальной, вернуть оригинал
Основное решение
public class RunLengthEncoding {
/**
* Сжимает строку методом Run-Length Encoding
*
* @param str исходная строка
* @return сжатая строка или оригинал, если сжатие неэффективно
*/
public static String compress(String str) {
// Шаг 1: Null и empty check
if (str == null || str.isEmpty()) {
return str;
}
// Шаг 2: Вычислить размер сжатой строки
int compressedLength = getCompressedLength(str);
// Если сжатая строка не короче, вернуть оригинал
if (compressedLength >= str.length()) {
return str;
}
// Шаг 3: Выполнить сжатие
StringBuilder compressed = new StringBuilder(compressedLength);
int count = 1;
for (int i = 0; i < str.length(); i++) {
// Если следующий символ отличается, добавляем текущий
if (i + 1 >= str.length() ||
str.charAt(i) != str.charAt(i + 1)) {
compressed.append(str.charAt(i));
compressed.append(count);
count = 1;
} else {
count++;
}
}
return compressed.toString();
}
/**
* Вычисляет размер сжатой строки БЕЗ фактического сжатия
* Оптимизация: если размер >= оригинального, останавливаемся
*/
private static int getCompressedLength(String str) {
int compressedLength = 0;
int count = 1;
for (int i = 0; i < str.length(); i++) {
// Если следующий символ отличается
if (i + 1 >= str.length() ||
str.charAt(i) != str.charAt(i + 1)) {
// Добавляем символ + количество (количество может быть 1-5 цифр)
compressedLength += 1 + String.valueOf(count).length();
count = 1;
} else {
count++;
}
// Оптимизация: если уже больше или равно оригиналу, можем вернуть
if (compressedLength >= str.length()) {
return str.length(); // Не имеет смысла сжимать
}
}
return compressedLength;
}
}
Тестирование
public class Main {
public static void main(String[] args) {
// Примеры, где сжатие эффективно
testCompress("aabcccccaaa", "a2b1c5a3");
testCompress("aaa", "a3");
testCompress("aaaa", "a4");
testCompress("aaaaaaaaaaaaabbbbccd", "a12b4c2d1");
// Примеры, где сжатие неэффективно (возвращаем оригинал)
testCompress("abcd", "abcd");
testCompress("abcdef", "abcdef");
testCompress("aabbcc", "aabbcc"); // a2b2c2 = 6 символов, оригинал = 6
// Edge cases
testCompress("", "");
testCompress("a", "a");
testCompress("aa", "aa"); // a2 = 2 символа, оригинал = 2 символа
}
private static void testCompress(String input, String expected) {
String result = RunLengthEncoding.compress(input);
String status = result.equals(expected) ? "✓" : "✗";
System.out.println(status + " Input: \"" + input + "\" → \"" +
result + "\" (expected: \"" + expected + "\")");
}
}
Декомпрессия
Часто нужно уметь распаковывать сжатую строку:
public class RunLengthDecoding {
/**
* Распаковывает строку из RLE формата
*
* @param compressed сжатая строка в формате RLE
* @return распакованная строка
*/
public static String decompress(String compressed) {
if (compressed == null || compressed.isEmpty()) {
return compressed;
}
StringBuilder decompressed = new StringBuilder();
int i = 0;
while (i < compressed.length()) {
char character = compressed.charAt(i);
i++;
// Читаем число
StringBuilder countStr = new StringBuilder();
while (i < compressed.length() &&
Character.isDigit(compressed.charAt(i))) {
countStr.append(compressed.charAt(i));
i++;
}
// Если нет числа, это ошибка
if (countStr.length() == 0) {
throw new IllegalArgumentException(
"Invalid RLE format at position " + (i - 1)
);
}
int count = Integer.parseInt(countStr.toString());
// Добавляем символ count раз
for (int j = 0; j < count; j++) {
decompressed.append(character);
}
}
return decompressed.toString();
}
}
// Тестирование декомпрессии
public class DecompressionTest {
public static void main(String[] args) {
testDecompress("a2b1c5a3", "aabcccccaaa");
testDecompress("a3", "aaa");
testDecompress("a12b4c2d1", "aaaaaaaaaaaaabbbbccd");
}
private static void testDecompress(String compressed, String expected) {
String result = RunLengthDecoding.decompress(compressed);
String status = result.equals(expected) ? "✓" : "✗";
System.out.println(status + " \"" + compressed + "\" → \"" +
result + "\"");
}
}
Анализ сложности
// Временная сложность: O(n)
// - Один проход по строке для вычисления размера: O(n)
// - Один проход по строке для сжатия: O(n)
// - Всего: O(n) + O(n) = O(n)
// Пространственная сложность: O(n)
// - StringBuilder хранит сжатую строку
// - В худшем случае (нет повторений) размер = оригинальному: O(n)
Оптимизированное решение
Можно оптимизировать, чтобы не вычислять размер дважды:
public class RunLengthEncodingOptimized {
public static String compress(String str) {
if (str == null || str.isEmpty()) {
return str;
}
// Проверяем и одновременно сжимаем
StringBuilder compressed = new StringBuilder();
int count = 1;
for (int i = 0; i < str.length(); i++) {
// Если следующий символ отличается, добавляем
if (i + 1 >= str.length() ||
str.charAt(i) != str.charAt(i + 1)) {
compressed.append(str.charAt(i));
compressed.append(count);
// Оптимизация: проверяем на лету
if (compressed.length() >= str.length()) {
return str; // Сжатие неэффективно
}
count = 1;
} else {
count++;
}
}
return compressed.toString();
}
}
Практические применения RLE
- Факс и черно-белые изображения — очень эффективно
- Текстовые файлы — часто неэффективно
- Последовательности ДНК — может быть эффективно
- Логотипы и графика — зависит от содержимого
- Телеметрия — если данные имеют паттерны
Сравнение с другими алгоритмами
- RLE — простой, быстрый, но не всегда эффективный
- Huffman Coding — лучше для текста, сложнее в реализации
- LZW — универсальный, используется в GIF/TIFF
- DEFLATE — основа ZIP, отличные соотношения (RLE + Huffman)
Ключевые моменты при реализации
- StringBuilder вместо String конкатенации — критично для производительности
- Проверка размера сжатой строки — не всегда сжатие полезно
- Правильная обработка чисел больше 9 — "a12" не "a1" + "2"
- Edge cases — пустая строка, один символ, четные последовательности
- Временная сложность O(n) — один или два прохода, не больше
Это классический алгоритм, часто появляющийся на интервью для проверки понимания циклов, строк и оптимизации памяти.