Algorithm 该算法的时间复杂度

Algorithm 该算法的时间复杂度,algorithm,time-complexity,Algorithm,Time Complexity,我一直在读一些关于时间复杂性的书,我已经涵盖了基本知识。为了强化这个概念,我看了一下我最近在这里给出的答案。这个问题现在结束了,这是有原因的,但这不是重点。我不知道我的答案有多复杂,在我得到任何有用的反馈之前,问题就结束了 查找字符串中的第一个唯一字符。我的回答很简单: public String firstLonelyChar(String input) { while(input.length() > 0) { int curLength = input

我一直在读一些关于时间复杂性的书,我已经涵盖了基本知识。为了强化这个概念,我看了一下我最近在这里给出的答案。这个问题现在结束了,这是有原因的,但这不是重点。我不知道我的答案有多复杂,在我得到任何有用的反馈之前,问题就结束了

查找字符串中的第一个唯一字符。我的回答很简单:

public String firstLonelyChar(String input)
{
    while(input.length() > 0)
    {
        int curLength = input.length();
        String first = String.valueOf(input.charAt(0));
        input = input.replaceAll(first, "");
        if(input.length() == curLength - 1)
            return first;
    }
    return null;
}

我的第一个想法是,由于它查看每个字符,然后在
replaceAll()
期间再次查看每个字符,所以它将是O(n^2)

然而,这让我开始思考它的实际工作方式。对于检查的每个字符,它将删除字符串中该字符的所有实例。因此,
n
在不断缩小。这一因素是如何影响的?这是O(对数n),还是我没有看到什么

我要问的是:

所写算法的时间复杂度是多少,为什么

我没有问的是:

我不是在寻找改进它的建议。我知道可能有更好的方法来做到这一点。我试图更好地理解时间复杂性的概念,而不是找到最佳解决方案。

最坏的情况是
O(n^2)
其中n是输入字符串的长度。想象一下,除了最后一个字符外,每个字符都加倍,比如“aabbccddeeffg”。然后有n/2次循环迭代,每次调用
replaceAll
都必须扫描整个剩余字符串,这也与
n
成比例


<>编辑:正如Ivaylo指出的,如果你的字母表的大小是恒定的,在技术上是代码>o(n)< /代码>,因为你从来没有考虑过任何一个字符。

< P>最坏的时间复杂度是对字符串<代码> AABB…< /代码>等,每个字符重复两次。这取决于字母表的大小,比如说
s
。让我们也用
L
注释初始字符串的长度。因此,对于每个字母,您必须迭代整个字符串。但是,第一次这样做时,字符串的大小将为
L
,第二次为
L-2
,依此类推。总的来说,您必须按照
L+(L-2)+…+的顺序执行(L-S*2)
操作,即
L*S-2*S*(S+1)
,假设L大于
2*S

顺便说一句,如果字母表的大小是恒定的,我想是的,那么代码的复杂性是
O(L)
(虽然有一个大的常数)。

让我们标记一下:

m=单词中唯一字母的数量
n=输入长度

这是复杂性计算:
主循环最多运行m次,因为有m个不同的字母,
.Replaceall在每个循环中最多检查O(n)个比较。

总数为:O(m*n)

O(m*n)循环的一个例子是:输入=aabbccdd

m=4,n=8

算法分为以下几个阶段:

1. input = aabbccdd, complex - 8
2. input = bbccdd, complex = 6
3. input = ccdd, complex = 4
4. input = dd, complex = 2

total=8+6+4+2=20=O(m*n)

m
为字母表的大小,让
n
为字符串的长度。更糟糕的情况是将字符串的字符均匀分布在字母表中,这意味着字母表中的每个字母都有
n/m
字符,让我们用
q
标记这个数量。例如,字符串
aabbccddefeffgghh
是字母
a-h
之间
16个
字符的均匀分布,因此这里
n=16
m=8
,每个字母都有
q=2个
字符

现在,您的算法实际上是遍历字母表中的字母(它只使用它们在字符串中出现的顺序),对于每次迭代,它必须遍历字符串的长度(
n
),并将其收缩
q
n-=q
)。因此,在最坏的情况下,您所做的所有操作都是:

s = n + n-(1*q) + ... + n-((m-1)*q)
您可以看到,
s
是算术序列的第一个
m
元素的总和:

s = (n + n-((m-1)*q) * m / 2 =  
    (n + n-((m-1)*(n/m)) * m / 2 ~ n * m / 2

我可以想象输入字符串的长度。我认为这取决于很多事情:1)每次
length()
是否必须对字符串中的每个字符进行计数,或者长度是单独存储的,这样就可以在不进行线性扫描的情况下返回?2) 如果
length()?3) 类使用什么类型的内存管理—擦除字符是简单的常量时间操作,还是需要对字符串的其余部分进行线性重新复制?4) 累积擦除何时会导致重新分配和复制?等等…@twalberg:我认为一个安全的假设是,
replaceAll
必须扫描整个字符串,因为这是“标准”实现,除非另有规定。@recursive Right-
replaceAll
当然必须扫描整个字符串以查找匹配项,但问题更多的是关于实际的擦除以及如何处理它——我可以想出三种同样有效的方法来从头开始编写
replaceAll
,但它们的效率并不相同(将未擦除的字符复制到临时字符串并复制回,或在每次擦除时将字符串尾部向左移动,或以某种方式简单地标记已擦除的字符以表示“此处无字符”)…@twalberg:鉴于它似乎没有进行就地擦除,(
输入=/*…*/
)你的三种方法都有运行时复杂度的其他线性吗?我不认为它是代码> n^ 2 < /代码>复杂性取决于字母表的大小-他将永远不会考虑一个字符不止一次,所以它更多的是<代码> L*S/<代码>的顺序,其中L是长度,<>代码> s>代码>是字母表的大小。如果是常数,那么复杂度就只有
O(N)
@IvayloStrandjev:这是一个很好的观点。就整个联通而言