找到最接近的具有相同权重 O(1) 的整数

Find the closest integer with same weight O(1)

我正在解决这个问题:

整数的二进制表示中1的个数称为该数的权重。以下算法找到具有相同权重的最接近的整数。例如,对于 123 (0111 1011)2,最接近的整数是 125 (0111 1101)2。

O(n) 的解决方案 其中 n 是输入数字的宽度,是通过交换第一对不同的连续位的位置来实现的。

有人可以给我一些在 O(1) 运行时和 space 中解决它的提示吗?

谢谢

正如 ajayv 已经评论的那样,这在 O(1) 中无法真正完成,因为答案总是取决于输入的位数。然而,如果我们将 O(1) 解释为意味着我们有一些原始整数数据作为输入,并且我们对该整数执行的所有逻辑和算术运算都是 O(1)(没有位循环),那么问题可以在常数时间内解决。当然,如果我们从 32 位整数更改为 64 位整数,运行 时间会增加,因为算术运算在硬件上需要更长的时间。

一种可能的解决方案是使用以下函数。第一个给你一个数字,其中只有 x 的最低设置位被设置

int lowestBitSet(int x){
  ( x & ~(x-1) ) 
}

第二个最低位未设置

int lowestBitNotSet(int x){
  return ~x & (x+1);
}

如果你在纸上做几个这样的例子,你就会明白它们是如何工作的。

现在您可以使用这两个函数找到您需要更改的位,然后使用您已经描述的算法。

C++ 实现(不检查没有答案的情况)

unsigned int closestInt(unsigned int x){
  unsigned int ns=lowestBitNotSet(x);
  unsigned int s=lowestBitSet(x);
  if (ns>s){
    x|=ns;
    x^=ns>>1;
  }
  else{
    x^=s;
    x|=s>>1;
  }
  return x;
}
static void findClosestIntWithSameWeight(uint x)
{
    uint xWithfirstBitSettoZero = x & (x - 1);
    uint xWithOnlyfirstbitSet = x & ~(x - 1);
    uint xWithNextTofirstBitSet = xWithOnlyfirstbitSet >> 1;

    uint closestWeightNum = xWithfirstBitSettoZero | xWithNextTofirstBitSet;

    Console.WriteLine("Closet Weight for {0} is {1}", x, closestWeightNum);

}

Java 解法:

//Swap the two rightmost consecutive bits that are different
for (int i = 0; i < 64; i++) {
        if ((((x >> i) & 1) ^ ((x >> (i+1)) & 1)) == 1) {
            // then swap them or flip their bits
            int mask = (1 << i) | (1 << i + 1);
            x = x ^ mask;
            System.out.println("x = " + x);
            return;
        }
    }

这个问题可以看作是“在数字的位表示中交换哪些不同的位,以便结果数字最接近原始数字?

因此,如果我们要交换索引 k1k2 处的位与 k2 > k1,数字之间的差异将是 2^k2 - 2^k1。我们的目标是最小化这种差异。假设位表示不是全 0 或全 1,一个简单的观察结果表明,如果我们将 |k2 - k1| 保持为最小值,则差异将最小。最小值可以是 1。因此,如果我们能够找到两个连续的不同位,从最低有效位(索引 = 0)开始,我们的工作就完成了。

从Least Significant Bit开始到最右边set bit全部为1的情况

                                   k2
                                   |
                         7 6 5 4 3 2 1 0
                         ---------------
                      n: 1 1 1 0 1 0 1 1

        rightmostSetBit: 0 0 0 0 0 0 0 1

     rightmostNotSetBit: 0 0 0 0 0 1 0 0 rightmostNotSetBit > rightmostSetBit so,

             difference: 0 0 0 0 0 0 1 0 i.e. rightmostNotSetBit - (rightmostNotSetBit >> 1):  
                         ---------------
         n + difference: 1 1 1 0 1 1 0 1

从Least Significant Bit开始到最右边set bit全部为0的情况

                                   k2
                                   |
                         7 6 5 4 3 2 1 0
                         ---------------
                      n: 1 1 1 0 1 1 0 0

        rightmostSetBit: 0 0 0 0 0 1 0 0

     rightmostNotSetBit: 0 0 0 0 0 0 0 1 rightmostSetBit > rightmostNotSetBit so,

             difference: 0 0 0 0 0 0 1 0 i.e. rightmostSetBit -(rightmostSetBit>> 1)
                         ---------------
         n - difference: 1 1 1 0 1 0 1 0

边缘情况,当然是全 0 或全 1 的情况。

    public static long closestToWeight(long n){
        if(n <= 0 /* If all 0s */ || (n+1) == Integer.MIN_VALUE /* n is MAX_INT */)
           return -1;
       long neg = ~n;
       long rightmostSetBit = n&~(n-1);
       long rightmostNotSetBit = neg&~(neg-1);
       if(rightmostNotSetBit > rightmostSetBit){
           return (n + (rightmostNotSetBit - (rightmostNotSetBit >> 1)));
       }
       return (n - (rightmostSetBit - (rightmostSetBit >> 1)));
}

python中的代码:

def closest_int_same_bit_count(x):
    if (x & 1) != ((x >> 1) & 1):
        return x ^ 0x3

    diff = x ^ (x >> 1)
    rbs = diff & ~(diff - 1)
    i = int(math.log(rbs, 2))

    return x ^ (1 << i | 1 << i + 1)

要在O(1)时间复杂度内解决这个问题可以认为有两个主要情况:

1) When LSB is '0':

在这种情况下,第一个“1”必须向右移动一个位置。

Input : "10001000" 

Out ::: "10000100" 

2) When LSB is '1':

在这种情况下,第一个“0”必须设置为“1”,第一个“1”必须设置为“0”。

Input : "10000111" 

Out ::: "10001110" 

Java 中的下一个方法代表一个解决方案。

private static void findClosestInteger(String word) { // ex: word = "10001000"

    System.out.println(word);           // Print initial binary format of the number
    int x = Integer.parseInt(word, 2);  // Convert String to int

    if((x & 1) == 0) {                  // Evaluates LSB value
        // Case when LSB = '0':
        // Input:  x = 10001000

        int firstOne = x & ~(x -1);     // get first '1' position (from right to left)
        // firstOne = 00001000
        x = x & (x - 1);                // set first '1' to '0'
        // x = 10000000
        x = x | (firstOne >> 1);        // "shift" first '1' with one position to right
        // x = 10000100

    } else {
        // Case when LSB = '1':
        // Input: x = 10000111

        int firstZero = ~x & ~(~x - 1); // get first '0' position (from right to left)
        // firstZero = 00001000
        x = x & (~1);                   // set first '1', which is the LSB, to '0'
        // x = 10000110
        x = x | firstZero;              // set first '0' to '1'
        // x = 10001110

    }

    for(int i = word.length() - 1; i > -1  ; i--) {       // print the closest integer with same weight
        System.out.print("" + ( ( (x & 1 << i) != 0) ? 1 : 0) );
    }
}

在 EPI 的问题 4.4 中可以找到对这个问题的很好的解释。
(编程面试要素)

如果您没有这本书,另一个地方是 geeksforgeeks.org 上的这个 link。
(这个时间复杂度可能有误link)

这里有两件事你应该记住(提示如果你想自己解决这个问题):
您可以使用 x & (x - 1) 清除最低设置位(不要与 LSB - 最低有效位混淆)
可以用x & ~(x - 1)到get/extract设置最低位

如果您知道 O(n) 解决方案,您就会知道我们需要找到与 LSB 不同的第一位的索引。
如果您不知道 LBS 是什么:

0000 0000
        ^ // it's bit all the way to the right of a binary string.

取二进制数1011 1000(十进制为184)
与 LSB 不同的第一位:

1011 1000
     ^ // this one

我们将其记录为 K1 = 0000 1000
然后我们需要将它与右边的下一位交换:

0000 1000
      ^ // this one

我们将其记录为 K2 = 0000 0100
按位或 K1 和 K2 在一起,你会得到一个掩码
mask = K1 | k2 // 0000 1000 | 0000 0100 -> 0000 1100
将掩码与原始数字按位异或,您将得到正确的 output/swap
number ^ mask // 1011 1000 ^ 0000 1100 -> 1011 0100

现在,在我们将所有内容放在一起之前,我们必须考虑这样一个事实,即 LSB 可能是 0001,之后的一堆位也可能是 1000 1111。所以我们必须处理第一位不同于LSB的两种情况;它可能是 1 或 0。

首先我们有一个条件来测试 LSB 是 1 还是 0:x & 1
IF 1 return x XORed with the return of a helper function
这个辅助函数有第二个参数,它的值取决于条件是否为真。 func(x, 0xFFFFFFFF) // if true // 0xFFFFFFFF 64 bit word with all bits set to 1
否则我们将跳过 if 语句和 return 类似的表达式,但为第二个参数提供不同的值。
return x XORed with func(x, 0x00000000) // 64 bit word with all bits set to 0. You could alternatively just pass 0 but I did this for consistency

我们的辅助函数 return 是一个掩码,我们将用它与原始数字进行异或以获得我们的输出。 它有两个参数,我们的原始数字和一个掩码,在这个表达式中使用:
(x ^ mask) & ~((x ^ mask) - 1)
这给了我们一个新数字,索引 K1 处的位始终设置为 1。
然后它将该位 1 向右移动(即索引 K2),然后将其与自身进行或运算以创建我们最终的掩码
0000 1000 >> 1 -> 0000 0100 | 0001 0000 -> 0000 1100

这一切都是用 C++ 实现的,如下所示:

unsigned long long int closestIntSameBitCount(unsigned long long int n)
{
  if (n & 1)
    return n ^= getSwapMask(n, 0xFFFFFFFF);
  return n ^= getSwapMask(n, 0x00000000);
}

// Helper function
unsigned long long int getSwapMask(unsigned long long int n, unsigned long long int mask)
{
  unsigned long long int swapBitMask = (n ^ mask) & ~((n ^ mask) - 1);
  return swapBitMask | (swapBitMask >> 1);
}

记下表达式(x ^ mask) & ~((x ^ mask) - 1) 我现在 运行 通过我的示例 1011 1000:

// start of closestIntSameBitCount
if (0) // 1011 1000 & 1 -> 0000 0000
// start of getSwapMask
getSwapMask(1011 1000, 0x00000000)
swapBitMask = (x ^ mask) & ~1011 0111  // ((x ^ mask) - 1) = 1011 1000 ^ .... 0000 0000 -> 1011 1000 - 1 -> 1011 0111
swapBitMask = (x ^ mask) & 0100 1000 // ~1011 0111 -> 0100 1000
swapBitMask = 1011 1000 & 0100 1000 // (x ^ mask) = 1011 1000 ^ .... 0000 0000 -> 1011 1000
swapBitMask = 0000 1000 // 1011 1000 & 0100 1000 -> 0000 1000
return swapBitMask | 0000 0100 // (swapBitMask >> 1) = 0000 1000 >> 1 -> 0000 0100
return 0000 1100 // 0000 1000 | 0000 0100 -> 0000 11000
// end of getSwapMask
return 1011 0100 // 1011 1000 ^ 0000 11000 -> 1011 0100
// end of closestIntSameBitCount

这里有一个完整的 运行ning 例子,如果你想自己编译 运行:

#include <iostream>
#include <stdio.h>
#include <bitset>

unsigned long long int closestIntSameBitCount(unsigned long long int n);
unsigned long long int getSwapMask(unsigned long long int n, unsigned long long int mask);

int main()
{
  unsigned long long int number;
  printf("Pick a number: ");
  std::cin >> number;
  std::bitset<64> a(number);
  std::bitset<64> b(closestIntSameBitCount(number));
  std::cout << a
            << "\n"
            << b
            << std::endl;
}

unsigned long long int closestIntSameBitCount(unsigned long long int n)
{
  if (n & 1)
    return n ^= getSwapMask(n, 0xFFFFFFFF);
  return n ^= getSwapMask(n, 0x00000000);
}

// Helper function
unsigned long long int getSwapMask(unsigned long long int n, unsigned long long int mask)
{
  unsigned long long int swapBitMask = (n ^ mask) & ~((n ^ mask) - 1);
  return swapBitMask | (swapBitMask >> 1);
}

尝试了 Python 中的问题。可以被视为 的翻译,并处理了边缘情况:

def closest_int_same_bit_count(x):
    # if all bits of x are 0 or 1, there can't be an answer
    if x & sys.maxsize in {sys.maxsize, 0}:
        raise ValueError("All bits are 0 or 1")

    rightmost_set_bit = x & ~(x - 1)
    next_un_set_bit = ~x & (x + 1)

    if next_un_set_bit > rightmost_set_bit:
        # 0 shifted to the right e.g 0111 -> 1011
        x ^= next_un_set_bit | next_un_set_bit >> 1
    else:
        # 1 shifted to the right 1000 -> 0100
        x ^= rightmost_set_bit | rightmost_set_bit >> 1

    return x

同样jigsawmnc的提供如下:

def closest_int_same_bit_count(x):
    # if all bits of x are 0 or 1, there can't be an answer
    if x & sys.maxsize in {sys.maxsize, 0}:
        raise ValueError("All bits are 0 or 1")

    rightmost_set_bit = x & ~(x - 1)
    next_un_set_bit = ~x & (x + 1)

    if next_un_set_bit > rightmost_set_bit:
        # 0 shifted to the right e.g 0111 -> 1011
        x += next_un_set_bit - (next_un_set_bit >> 1)
    else:
        # 1 shifted to the right 1000 -> 0100
        x -= rightmost_set_bit - (rightmost_set_bit >> 1)

    return x

这是我解决问题的方法。我想@jigsawmnc 很好地解释了为什么我们需要 |k2 -k1|到最低限度。因此,为了找到最接近的具有相同权重的整数,我们需要找到翻转连续位的位置,然后再次翻转它们以获得答案。为此,我们可以移动 1 号单元。取同数异或。这将在所有存在翻转的位置设置位。找到 XOR 的最低有效位。这将为您提供最小的翻转位置。为位置和下一位创建掩码。采用异或运算,这应该就是答案。如果数字全为 0 或全为 1,这将不起作用 这是它的代码。

    def variant_closest_int(x: int) -> int:
        if x == 0 or ~x == 0:
            raise ValueError('All bits are 0 or 1')
        x_ = x >> 1
        lsb = x ^ x_
        mask_ = lsb & ~(lsb - 1)
        mask = mask_ | (mask_ << 1)
        return x ^ mask

我的解决方案利用了整数的奇偶校验。我认为我得到 LSB masks 的方式可以简化

def next_weighted_int(x):
    
    if x % 2 == 0:
        lsb_mask = ( ((x - 1) ^ x) >> 1 ) + 1 # Gets a mask for the first 1
        x ^= lsb_mask
        x |= (lsb_mask >> 1)
        
        return x
    
    lsb_mask = ((x ^ (x + 1)) >> 1 ) + 1 # Gets a mask for the first 0
    x |= lsb_mask
    x ^= (lsb_mask >> 1)

    return x

只是分享我对这个问题的 python 解决方案:

def same closest_int_same_bit_count(a):
    x = a + (a & 1)  # change last bit to 0
    bit = (x & ~(x-1)) # get last set bit
    return a ^ (bit | bit >> 1) # swap set bit with unset bit
func findClosestIntegerWithTheSameWeight2(x int) int {
        rightMost0 := ^x & (x + 1)
        rightMost1 := x & (-x)
        if rightMost0 > 1 {
            return (x ^ rightMost0) ^ (rightMost0 >> 1)
        } else {
            return (x ^ rightMost1) ^ (rightMost1 >> 1)
        }
    }