算法
可以考虑使用二分查找。我已经实现了以下算法来确定给定非负整数n
中最长的1位字符串的长度。计算复杂度为O(n
),但对于许多n
的值,它将接近O(log2(n
))。
算法步骤如下,但读者可能会更容易地通过先阅读以下部分(“说明”)来跟随它们。
- 将
longest
设置为0
。
- 将
len
设置为n
的位数(len = n.bit_length
)。
- 如果
len <= 2
,则返回答案(0
、1
或2
)。
- 确定中间位
n
的索引mid
(mid = len/2
或mid = len >> 1
)。
- 将
ri = mid+1
和li = mid-1
。
- 如果中间位的值(
n[mid]
)为零,则转到步骤10。
- (
n[mid] = 1
才能到达此步骤。)确定最大索引li >= mid
和最小索引ri <= mid
,使得对于所有ri <= i <= li
,都有n[i] = 1
。
- 设置
longest = [longest, ri-li+1].max
,li += 1
和ri -= 1
。
- 如果
li == len
,则转到步骤10;否则,将ln
设置为由索引li
(最不重要的)到len-1
(最重要的)的位组成的数字。递归地将n
设置为ln
并转到步骤2。这将返回数字ln
中最长的1位字符串(cand
)。设置longest = [longest, cand].max
。
- 如果
ri < 0
,则转到步骤11;否则,将rn
设置为由索引0
(最不重要的)到ri
(最重要的)的位组成的数字。递归地将n
设置为rn
并转到步骤2。这将返回数字rn
中最长的1位字符串(cand
)。设置longest = [longest, cand].max`
。
- 在递归中返回
longest
。
说明
假设
n = 0b1010011011101011110
那么
len = n.bit_length
mid = len >> 1
n[mid]
longest = 0
我们可以将n
描述如下
101001101 1 101011110
其中中间的位 1
很显眼。由于它是一个 1
,我们向左右寻找连续的 1 序列。我们得到以下结果。
10100110 111 01011110
由于我们发现了三个1,因此我们更新longest
。
longest = [longest, 3].max
我们现在需要检查
0b10100110
和
0b1011110
(第二个数字的前导零被舍去)。
对于左边的数字,我们做如下计算。
n = 0b10100110
len = n.bit_length
mid = len >> 1
n[mid]
所以我们有
101 0 0110
(注意
n [0]
是最低有效位)。 我们可以排除
0b101
和
0b110
,因为两者都有
3
位,这不超过当前值
longest
的
3
。
现在我们考虑右半部分,
n = 0b1011110
len = n.bit_length
mid = len >> 1
n[mid]
所以我们有
101 1 110
由于 n[mid] #=> 1
,我们向左右查找连续的1字符串并获得
10 1111 0
当我们发现了一个由4
个1组成的字符串时,我们设置
longest = [longest, 4].max
最后我们可以看到左侧数字的位数(
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
longest_run 0b1011110011111000000111100011101111011110
longest_run 0b101111001111100000011110001110111111011111
longest_run 2**500_000-1
longest_run ("10"*100_000).to_i(2)
@@
类型的变量非常不寻常。你有充分的理由这样做吗? - tadman@
变量来保持组织。@@
是类变量。 - tadman