【问题标题】:Integer in an interval with maximized number of trailing zero bits具有最大尾随零位数量的区间中的整数
【发布时间】:2011-09-03 15:09:28
【问题描述】:

Sought 是一种高效算法,可在区间 [a, b] 中找到唯一整数,该区间在其二进制表示中具有最大数量的尾随零(ab 是大于 0 的整数):

def bruteForce(a: Int, b: Int): Int =
  (a to b).maxBy(Integer.numberOfTrailingZeros(_))

def binSplit(a: Int, b: Int): Int = {
  require(a > 0 && a <= b)
  val res = ???
  assert(res == bruteForce(a, b))
  res
}

这里有一些例子

bruteForce(  5,   7) ==   6 // binary 110 (1 trailing zero)
bruteForce(  1, 255) == 128 // binary 10000000
bruteForce(129, 255) == 192 // binary 11000000

等等

【问题讨论】:

    标签: algorithm scala bit-manipulation


    【解决方案1】:

    作为结论,这是我的 Rex 答案的变体,它既给出了中心值,也给出了一个“范围”,它是距中心两个距离的最小功率,在一个方向上覆盖 a 和 @ 987654322@在对方。

    @tailrec def binSplit(a: Int, b: Int, mask: Int = 0xFFFFFFFF): (Int, Int) = {
      val mask2 = mask << 1
      if (a > (b & mask2)) (b & mask, -mask)
      else binSplit(a, b, mask2)
    }
    
    def test(): Unit = {
      val Seq(r1, r2) = Seq.fill(2)(util.Random.nextInt(0x3FFFFFFF) + 1)
      val (a, b) = if (r1 <= r2) (r1, r2) else (r2, r1)
      val (center, extent) = binSplit(a, b)
      assert((center >= a) && (center <= b) && (center - extent) <= a &&
             (center - extent) >= 0 && (center + extent) > b, (a, b, center, extent))
    }
    
    for (i <- 0 to 100000) { test() }
    

    【讨论】:

      【解决方案2】:

      我决定接受 Rex 的挑战并更快地制作出一些东西。 :-)

      // requires a > 0
      def mtz2(a: Int, b: Int, mask: Int = 0xffff0000, shift: Int = 8, n: Int = 16): Int = {
        if (shift == 0) if (a > (b & mask)) n - 1 else n
        else if (a > (b & mask)) mtz2(a, b, mask >> shift, shift / 2, n - shift)
        else mtz2(a, b, mask << shift, shift / 2, n + shift)
      }
      

      为基准
      import System.{currentTimeMillis => now}
      def time[T](f: => T): T = {
        val start = now
        try { f } finally { println("Elapsed: " + (now - start)/1000.0 + " s") }
      }
      
      val range = 1 to 200
      time(f((a, b) => mtz(a, b)))
      time(f((a, b) => mtz2(a, b)))
      

      【讨论】:

      • 不错!使用 10000000 的循环和预先计算的随机间隔,mtz 在 REPL 中预热约 0.74 秒和 mtz2 约 0.52 秒后运行,加速大约 30%。
      • 不错 :) (P.S. 这个问题是我第一次在我欣赏有符号移位运算符的地方做点小玩意......)
      • @Rex 其实我也是。 :-) 我看到我错过了上面代码中的f 定义。我会看看周一是否还能在我的工作桌面上找到它。
      【解决方案3】:

      这个找零的个数:

      // Requires a>0
      def mtz(a: Int, b: Int, mask: Int = 0xFFFFFFFE, n: Int = 0): Int = {
        if (a > (b & mask)) n
        else mtz(a, b, mask<<1, n+1)
      }
      

      这个返回带有那些零的数字:

      // Requires a > 0
      def nmtz(a: Int, b: Int, mask: Int = 0xFFFFFFFE): Int = {
        if (a > (b & mask)) b & (mask>>1)
        else nmtz(a, b, mask<<1)
      }
      

      我怀疑 log(log(n)) 解决方案有一个足够小的常数项来击败它。 (但您可以对零的数量进行二进制搜索以获得 log(log(n))。)

      【讨论】:

      • 哇,太棒了!你是刚想出这个还是你有那个?这是某种有名字的标准算法吗?
      • @Rex:这看起来很接近我的叙述版本。如果它相同,那么在每一步之后,您将减少尾随零的数量。所以根本不需要搜索?
      • @Sciss - 我刚想出这个。它可能是以第一个提出并命名它的人的名字命名的。 @John - 没有必要做“如果更少,如果更多”的事情,因为掩蔽总是会减少 b。
      • @Rex 我不是 Scala 程序员,所以我需要研究你的代码。 :) 但我只是不明白你关于“你可以进行二进制搜索”的评论。
      • @John - 我从0xFFFFFFFF - ((1&lt;&lt;1) - 1) 开始,即一个零,并不断增加零的数量,直到发生某些事情。但我也可以从 16 个零开始 ((1&lt;&lt;16)-1),如果标准失败跳到 8,或者如果成功跳到 24,然后以这种方式对零的数量进行二进制搜索(当我有n 工作和 n+1 失败的对)。这将提供O(log(32)) 步骤,但工作量更大。这可能是 64 位整数的胜利。
      【解决方案4】:

      首先看看你的区间内是否有 2 的幂。如果至少有一个,则最大的获胜。

      否则,请选择小于最小界限的 2 的最大幂。

      1100000...0 是否在您的范围内?如果是,你就赢了。如果它仍然小于您的最低限度,请尝试 1110000...0;否则,如果它大于您的最大界限,请尝试 1010000...0。

      以此类推,直到你获胜。

      【讨论】:

      • 好的,谢谢。因此,Rex' 和您的方法都将采用最坏的情况 O(log n),其中 n 是最大位数 (32)。我希望有一些 O(1) 的魔法,但这实际上很好。
      • 比特数不是 O(n) 吗?如果您将 n 设为您的间隔最大值,我可以看到 O(log n)?还是我把事情搞糊涂了?
      • 是的,完全正确。由于我的数字有一个固定的字长(不变的恒定位数),这实际上意味着它是 O(1) 关于间隔的大小。
      猜你喜欢
      • 1970-01-01
      • 2014-01-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-02-06
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多