算法
人们可能会考虑使用二分搜索。我已经实现了以下算法来确定给定非负整数中最长 1 位字符串的长度n。计算复杂度为 O(· n),但对于许多值,n它将接近 O(log 2 ( · n))。
该算法的步骤如下,但许多读者发现它们更容易通过首先阅读以下部分(“插图”)来理解。
- 设置
longest为0。
- 设置等于( )
len中的位数。nlen = n.bit_length
- 如果
len <= 2,则返回答案(0,1或2)。
- 确定(或)
mid中间位的索引。nmid = len/2mid = len >> 1
- 设置
ri = mid+1和li = mid-1。
- 如果中间位 (
n[mid]) 的值为零,则转到步骤 10。
- (
n[mid] = 1达到这一步。)确定最大索引li >= mid和最小索引ri <= mid,使得n[i] = 1所有ri <= i <= li.
- 设置和。
longest = [longest, ri-li+1].max_li += 1ri -= 1
- 如果
li == len转到步骤 10;否则设置ln等于由索引li(最低有效)到len-1(最高有效)处的位组成的数量。递归设置为n并ln转到步骤 2。这将返回数字ln( cand) 中最长的 1 位字符串。设置longest = [longest, cand].max。
- 如果
ri < 0进行步骤11;否则设置rn等于由索引0(最低有效)到ri(最高有效)处的位组成的数量。递归设置为n并rn转到第 2 步。这将返回第一个数字中最长的 1 位字符串rn (candlonglong ). Set= [longest, cand].max`。
longest在递归中返回。
插图
认为
n = 0b1010011011101011110
#=> 341854
然后
len = n.bit_length
#=> 19
mid = len >> 1
#=> 9
n[mid]
#=> 1
longest = 0
我们可以n如下图
101001101 1 101011110
中间位1突出的地方。因为它是 a 1,所以我们向左和向右寻找 1 的序列。我们得到以下。
10100110 111 01011110
当我们找到三个 1 时,我们更新longest.
longest = [longest, 3].max
#=> [0, 3].max => 3
我们现在必须检查0b10100110和0b1011110(第二个数字中的前导零丢失)。
对于左边的数字,我们计算如下。
n = 0b10100110
len = n.bit_length
#=> 8
mid = len >> 1
#=> 4
n[mid]
#=> 0
所以我们有
101 0 0110
(注意n[0]是最不重要的位)。我们可以排除0b101和0b110,因为两者都有3位,不超过 的当前值longest,即3。
现在我们考虑右半部分,
n = 0b1011110
len = n.bit_length
#=> 7
mid = len >> 1
#=> 3
n[mid]
#=>1
所以我们有
101 1 110
作为n[mid] #=> 1,我们左右查找 1 的字符串并获得
10 1111 0
由于我们发现了一个41 的刺,我们设置
longest = [longest, 4].max
#=> [3, 4].max = 4
2最后我们看到左边 ( ) 和右边 ( )的位数1都小于3,所以我们完成了,返回longest #=> 4。(OP 实际上想要longest - 1 #=> 3,我们认为这是一个侧面计算。)
代码
def longest_run(n, longest=0)
len = n.bit_length
return [longest, (n & 1) + (n >> 1)].max if len <= 2
mid = len >> 1
ri = mid-1
li = mid+1
if n[mid] == 1
until n[ri] == 0
ri -= 1
end
until n[li] == 0
li += 1
end
cand = li-ri-1
longest = cand if cand > longest
end
if ri >= 0
shift = ri+1
m = n ^ ((n >> shift) << shift)
if m.bit_length > longest
cand = longest_run(m, longest)
longest = cand if cand > longest
end
end
if li <= len - 1
m = n >> li
if m.bit_length > longest
cand = longest_run(m, longest)
longest = cand if cand > longest
end
end
longest
end
例子
longest_run 341854
#=> 4
longest_run 0b1011110011111000000111100011101111011110
#=> 5
longest_run 0b101111001111100000011110001110111111011111
#=> 6
longest_run 2**500_000-1
#=> 500000
longest_run ("10"*100_000).to_i(2)
#=> 1