#java #recursion #backtracking
Вопрос:
Я пытаюсь понять временную сложность для этого кода, который вычисляет IP-адреса для данной строки, разделив строку на 4 части. Каждая часть разделена точкой, т. е. .
public List<String> restoreIpAddresses(String s, int parts) {
List<String> result = new ArrayList<>();
if (parts == 1) {
if (isValidPart(s)) result.add(s);
return resu<
}
for (int i = 0; i < s.length(); i ) {
String first = s.substring(0, i);
if (!isValidPart(first)) {
continue;
}
List<String> previous = restoreIpAddresses(s.substring(i, s.length()), parts - 1);
for (String str: previous) {
StringBuilder sb = new StringBuilder();
result.add(first "." str);
}
}
return resu<
}
private boolean isValidPart(String part) {
if ( (part.length() > 1 amp;amp; part.startsWith("0")) ||
(part.length() > 3) || (part.length() == 0)
(Integer.valueOf(part) > 255) ) return false;
return true;
}
}
Поскольку цикл for O(n)
равен , n-это длина строки, и на каждой итерации цикл for выполняется для подстроки, переданной в родительском цикле for, поэтому O(n - 1)
. Так что по этой логике временная сложность должна быть n(n-1)(n-2) ....1
, т. е. n!
в худшем случае, верно?
Однако, если я оглянусь (например, здесь или здесь), я увижу, что люди публикуют постоянную временную сложность. Я не в состоянии понять. Кто-нибудь может помочь мне разобраться в этом?
Ответ №1:
Учтите, что для генерации IP-адресов из приведенного выше алгоритма у нас есть два ограничения.
- Допустимый IP-адрес от 0 до>255. Это можно оценить в течение постоянного времени.
- Там будет 4 октета. Таким образом, строка вопроса должна быть разделена на 4 части.
Теперь рассмотрим строку 111 111 111 111
длины 12
- Сколько способов вы можете сформировать первый октет? => минимум 1 , максимум 3 варианта из 12 символов.
complexity:- O(3)
- Сколько способов вы можете сформировать второй октет? => минимум 0 максимум 3 способа из 9 символов, учитывая, что в первом октете используются 3 символа.
complexity:- O(3)
- Сколько способов вы можете сформировать третий октет? => минимум 0 максимум 3 способа из 6 символов, учитывая, что 6 символов используются первым и вторым октетом.
complexity:- O(3)
- Сколько способов вы можете сформировать четвертый октет из оставшихся символов? => Существует только один способ сформировать октет из оставшихся 3 символов. учитывая, что в первом, втором и третьем октете используется 9 символов.
O(1)
Расчет временной сложности.
Time Complexity = product of complexities of each recursive function
= O(3)*O(3)*O(3)*O(1)
= 3*O(3) = O(1) = [constant-time] complexity
Таким образом, независимо от того, какую строку вы будете вводить в качестве входных данных, все действительные IP-адреса могут быть подсчитаны в 27
итерациях. Поэтому этот алгоритм является постоянным по времени O(1)
.
Учитывая вышеизложенное, код может быть переписан следующим образом
public static List<String> restoreIpAddresses(String s, int position, int parts) {
List<String> result = new ArrayList<>();
// O(1) time complexity
if (parts == 1) {
if (position < s.length() amp;amp; isValidPart(s.substring(position))) {
result.add(s.substring(position));
}
return resu<
}
// Iterate only thrice in each recursive function. O(3) time complexity
for (int i = position; i <= position 3; i ) {
if (i > s.length()) {
continue;
}
String first = s.substring(position, i);
if (!isValidPart(first)) {
continue;
}
List<String> previous = restoreIpAddresses(s, i , parts - 1);
for (String str : previous) {
StringBuilder sb = new StringBuilder();
result.add(first "." str);
}
}
return resu<
}
Обратите внимание, что приведенный выше алгоритм является одним из примеров классического backtracking problems
. Из вики.
Обратное отслеживание-это общий алгоритм поиска решений некоторых вычислительных задач, в частности проблем с удовлетворением ограничений, который постепенно создает кандидатов для решений и отбрасывает кандидата («обратное отслеживание»), как только он определяет, что кандидат не может быть завершен до допустимого решения
PS:- Пример 111 111 111 111
является крайним примером, и 111.111.111.111
из этой строки может быть сформирован только один действительный IP-адрес. Однако оценка цикла/рекурсии будет выполняться максимум 81 раз.
Комментарии:
1. Таким образом, код, который я написал выше, точно не возвращается, если я не использую параметр position?
2. Код, который вы написали, такой же, как у меня,
isValidPart
проверяет длину 3, хотя ваш код не повторяется более 3, поэтому меньше проверок. Но тот, который у меня есть, все еще возвращается, верно? основываясь на определении?3. Оба кода, ваш и мой, одинаковы. Я оптимизировал его, чтобы он не выходил за рамки 3. Как вы видите, любая подстрока длиной более 3 не является допустимым IP-адресом. Для длины, превышающей 3, значение
isValidPart
всегда будет возвращать значение false.4. Да, код, который мы с вами написали, по сути
backtracking
, таков . Если вы видите, что мы принимаем решение, решите, приведет ли это к решению, если нет, мы откажемся. Что означает сделать один шаг назад в дереве рекурсии здесь.5. Спасибо! Можете ли вы добавить некоторую информацию во время выполнения, если это была более общая проблема. В принципе, правильна ли моя оценка времени выполнения, когда я использую факториал в общем случае?