如何解决用迭代计算递归函数的时间复杂度
我试图了解这段代码的时间复杂度,该代码通过将字符串分成 4 部分来计算给定字符串的 IP 地址。每个部分由句点分隔,即 .
public List<String> restoreIpAddresses(String s,int parts) {
List<String> result = new ArrayList<>();
if (parts == 1) {
if (isValidPart(s)) result.add(s);
return result;
}
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 result;
}
private boolean isValidPart(String part) {
if ( (part.length() > 1 && 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!
,对吧?
但是,如果我环顾四周(例如 here 或 here),我会看到人们发布恒定的时间复杂度。我无法理解。有人可以帮我分解一下吗?
解决方法
考虑到从上述算法生成 IP 地址,我们有两个约束。
- 有效 IP 为 0->255。这可以在恒定时间内进行评估。
- 将有 4 个八位字节。所以问题字符串应该分为4部分。
现在考虑长度为 111 111 111 111
12
-
有多少种方法可以组成第一个八位字节? => 最少 1 ,最多 3 种方式,共 12 个字符。
complexity:- O(3)
-
你有多少种方式可以组成第二个八位字节? => 9 个字符中的最小 0 最大 3 种方式,考虑到第一个八位字节使用了 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() && isValidPart(s.substring(position))) {
result.add(s.substring(position));
}
return result;
}
// 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 result;
}
注意上述算法是经典backtracking problems
的一个例子。来自维基。
回溯是一种通用算法,用于寻找某些问题的解决方案 计算问题,特别是约束满足问题,即 逐步构建解决方案的候选者,并放弃一个 候选人(“回溯”)一旦确定候选人 不可能完成一个有效的解决方案
PS:- 示例 111 111 111 111
是一个极端示例,并且只能从该字符串中形成一个有效的 IP 111.111.111.111
地址。但是,循环/递归评估最多会发生 81 次。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。