摘自编码面试:
您将如何实现具有increment
O(1) 时间复杂度的无限二进制计数器?
我想计算最右边的第一个和第二个位置0
,但我不确定如何实现这个。
“无限计数器”意味着您可以增加无限次(大于 MAX_INT)。
摘自编码面试:
您将如何实现具有increment
O(1) 时间复杂度的无限二进制计数器?
我想计算最右边的第一个和第二个位置0
,但我不确定如何实现这个。
“无限计数器”意味着您可以增加无限次(大于 MAX_INT)。
对于二进制计数器...
如果您想将计数器保持在“正常”位模式,从根本上讲,您不能 - 至少不是总是O(1) 而不是摊销O(1)。
如果它是一个无限计数器,它可以有任意多的位。这意味着您可以拥有多个 N 位,所有位均为 1。递增该计数器意味着将所有这些位设置为 0,可以合理地假设为 O(N) 操作。
我们可以认为在“正常”计算中增量为 O(1) 的唯一原因是通常处理固定大小的类型,我们可以说(例如)“最多 32 位需要更改 - 这是一个常数,因此可以想象这是一个 O(1) 操作。”
只是一个柜台...
另一方面,如果您只想能够在 O(1) 时间内递增,那么您拥有无限的内存,并且您不在乎恢复值需要多长时间,您可以做到这一点,只需有效地使用一个链表,其长度是计数器大小。
例如,在 C# 中:
public DodgySolution
{
public static DodgySolution Zero = new DodgySolution(null);
private DodgySolution tail;
private DodgySolution(DodgySolution tail)
{
this.tail = tail;
}
// This bit is O(1)
public DodgySolution Increment()
{
return new DodgySolution(this);
}
// This bit isn't...
public BigInteger ToBigInteger()
{
return tail == null ? BigInteger.Zero
: BigInteger.One + tail.ToBigInteger();
}
}
即使这样也假设引用分配是 O(1) - 这可能会因为无限数量的对象而变得棘手......
所以一个直接的实现已经摊销了 O(1) 的性能。唯一的问题是您需要可变存储。
在查看 number 的单个增量操作时n
,平均时间是 O(1),但最坏的情况是 O(log(n))。内存使用量为 O(log(n))。
var counter=new List<bool>{false};
void Inc()
{
while(counter[i])
{
counter[i]=false;
i++;
}
if(i==counter.Length)
counter.Add(true);
else
counter[i]=true;
}
如果问题只是要求增加 O(1) 计数器而没有任何其他限制,则您的计数器可以实现为数字的链接列表,并且项目的总和是您的计数器的值。
如果之前的值大于 (Max-1),则递增将等同于在最后一项中添加 1 或添加新项 = 1。
由于您总是最多检查列表中的 2 个项目,因此递增将是 O(1)
只是不要尝试用你闪亮的新计数器做其他算术:D
我的尝试:
我们在连续的1
or上保持聚合0
。
意思是 111000111 是 <1,0> <3,1> <3,0> <3,1>
我可以用以下 DS 来表示这一点:
节点列表 { digit : bool, counter: long}
1) 如果第一个批量是 1。它变成大量的 0 并将下一个 0 变成 1。
我们现在检查是否可以聚合大量的 1。
2) 如果第一个块是 0,我们将第一个数字设为 1。看看我们是否可以聚合 1。
示例 A:
意思是 111000111 是 <1,0> <3,1> <3,0> <3,1>
读数:1
三位数,0
三位数,1
三位数,一位0
数
增量()
<1,0> <3,1> <2,0> <1,1> <3,0>
示例 B:
<1,0> <3,1> <1,0> <3,1>
增量()
<1,0> <3,1> <1,1> <3,0>
聚合:
<1,0> <4,1> <3,0>
总会有 const 的更改次数(直到最右边的 0 位)
并且转动大部分1
s 只是切换布尔成员。这是恒定的