Итак, я столкнулся с проблемой, которая заявляет. «Определить, содержит ли строка все уникальные символы»

Поэтому я написал это решение, которое добавляет каждый символ в набор, но если символ уже существует, он возвращает false.

private static boolean allUniqueCharacters(String s) {

    Set<Character> charSet = new HashSet<Character>();
    for (int i = 0; i < s.length(); i++) {
        char currentChar = s.charAt(i);
        if (!charSet.contains(currentChar)) {
            charSet.add(currentChar);

        } else {
            return false;
        }

    }
    return true;

}

Согласно книге, которую я читаю, это «оптимальное решение»

public static boolean isUniqueChars2(String str) {
    if (str.length() > 128)
        return false;

    boolean[] char_set = new boolean[128];

    for (int i = 0; i < str.length(); i++) {
        int val = str.charAt(i);

        if (char_set[val]) {
            return false;
        }
        char_set[val] = true;
    }

    return true;
}

Мой вопрос, моя реализация медленнее, чем представленная? Я предполагаю, что это так, но если поиск Hash - это O (1), разве они не будут той же сложности?

Спасибо.

18
fsdff 21 Авг 2018 в 10:39

6 ответов

Лучший ответ

Как сказал Амадан в комментариях, оба решения имеют одинаковую сложность по времени O (n), потому что у вас есть цикл for для цикла по строке, и вы выполняете операции с постоянным временем в цикле for. Это означает, что время, необходимое для запуска ваших методов, увеличивается линейно с длиной строки.

Обратите внимание, что временная сложность заключается в том, как время изменяется , когда вы меняете размер ввода. Дело не в том, насколько быстро это происходит с данными одинакового размера.

Для той же строки «оптимальное» решение должно быть быстрее, потому что наборы имеют некоторые издержки над массивами. Обработка массивов происходит быстрее, чем обработка множеств. Однако для того, чтобы «оптимальное» решение работало, вам понадобится массив длиной 2 ^ 16. Вот сколько существует различных char значений. Вам также необходимо удалить проверку для строки длиннее 128.

Это один из многих примеров компромисса между пространством и временем. Если вы хотите, чтобы он шел быстрее, вам нужно больше места. Если вы хотите сэкономить место, вы должны идти медленнее.

12
Sweeper 21 Авг 2018 в 07:58

Хеш-карта в теории приемлема, но это пустая трата времени.

Хеш-карта создается на основе массива (поэтому он, безусловно, более дорогостоящий, чем массив), а для разрешения коллизий требуется дополнительное пространство (как минимум, вдвое больше, чем количество элементов). Кроме того, любой доступ требует вычисления хеша и, возможно, разрешения коллизий.

Это добавляет много накладных расходов с точки зрения пространства и времени по сравнению с прямым массивом.

Также обратите внимание, что это своего рода фольклор, что хеш-таблица имеет поведение O (1). В худшем случае гораздо хуже, доступ может занять до O (N) времени для таблицы размера N.


В заключение отметим, что временная сложность этого алгоритма равна O (1), поскольку при N> 128 вы делаете вывод в худшем случае.

2
Yves Daoust 21 Авг 2018 в 13:34

Ваш алгоритм также O(1). Вы можете думать о сложности, как how my algorithm will react to the change in amount of elements processed. Следовательно, O(n) и O(2n) фактически равны.

Люди говорят о нотации O как о скорости роста здесь

1
amerykanin 21 Авг 2018 в 07:48

Узким местом вашей реализации является то, что набор имеет сложность поиска (и вставки) *, равную O(log k), в то время как массив имеет сложность поиска в O(1).

Похоже, ваш алгоритм должен быть намного хуже. Но на самом деле это не так, поскольку k ограничен 128 (иначе эталонная реализация будет неправильной и приведет к ошибке за пределами допустимой границы) и может рассматриваться как константа. Это делает поиск по множеству O(1) также с немного большими константами, чем поиск по массиву.

* при условии разумной реализации в виде дерева или hashmap. Сложность времени в хэш-карте обычно не постоянна, так как для ее заполнения требуется log(n) операция изменения размера, чтобы избежать увеличения коллизий, которые могут привести к линейному времени поиска, см., Например, здесь и здесь для ответов на stackoverflow.

В этой статье даже объясняется что java 8 сам по себе преобразует хэш-карту в двоичное дерево (O(n log n) для разговора, O(log n) для поиска) до того, как время его поиска выродится в O(n) из-за слишком большого количества коллизий.

0
allo 21 Авг 2018 в 14:39

Оба алгоритма имеют временную сложность O (N). Разница заключается в их пространстве сложности.

Решение книги всегда будет требовать хранения 128 символов - O(1), в то время как требования к пространству вашего решения будут линейно варьироваться в зависимости от ввода - O(N).

Требуемое место в книге основано на предполагаемом наборе символов из 128 символов. Но это может быть довольно проблематичным (и не масштабируемым), учитывая вероятность того, что понадобятся разные наборы символов.

3
ernest_k 21 Авг 2018 в 08:02

Ваше решение действительно может быть медленнее, чем решение в книге. Во-первых, поиск по хэшу в идеале должен иметь постоянный поиск по времени. Но извлечения объекта не будет, если будет несколько хеш-коллизий. Во-вторых, даже если это поиск в постоянное время, выполнение функции хэш-кода обычно сопряжено со значительными накладными расходами по сравнению с поиском элемента в массиве по индексу. Вот почему вы можете захотеть воспользоваться поиском по массиву. Однако, если вы начнете иметь дело с символами Unicode, отличными от ASCII, вы, возможно, не захотите использовать массивный подход из-за значительных накладных расходов на пространство.

1
entpnerd 21 Авг 2018 в 07:52
51943836