我上次面试时收到的一个问题:
设计一个函数
f
,使得:f(f(n)) == -n
当
n
是一个32位的有符号整数时,你不能使用复数运算。如果你不能为所有数字范围设计这样的函数,那就尽可能地为最大范围设计。
有什么想法吗?
我实际上并不是在试图给出问题本身的解决方案,但是我有一些评论。正如问题所述,这个问题是作为(工作?)面试的一部分提出的。
int.MinValue
到int.MaxValue
,并对该范围内的每个n
调用f(f(n))
,并检查结果是否为-n
),告诉面试官我会使用测试驱动开发来得到这样的函数。针对一个介于2^32 - 1之间的范围,使用C#进行操作。该范围内的所有数字均为int32类型,除了(Int32.MinValue)。
Func<int, int> f = n =>
n < 0
? (n & (1 << 30)) == (1 << 30) ? (n ^ (1 << 30)) : - (n | (1 << 30))
: (n & (1 << 30)) == (1 << 30) ? -(n ^ (1 << 30)) : (n | (1 << 30));
Console.WriteLine(f(f(Int32.MinValue + 1))); // -2147483648 + 1
for (int i = -3; i <= 3 ; i++)
Console.WriteLine(f(f(i)));
Console.WriteLine(f(f(Int32.MaxValue))); // 2147483647
输出:
2147483647
3
2
1
0
-1
-2
-3
-2147483647
这个函数实际上需要将可用范围分成大小为4的周期,其中-n位于n的周期的对面。然而,0必须是大小为1的一个周期的一部分,否则 0->x->0->x != -x
。由于0是孤独的,因此我们的范围(其大小为4的倍数)中必须有另外3个值不在适当的4元素周期内。
我选择这些额外的奇怪值为MIN_INT
,MAX_INT
和MIN_INT+1
。此外,MIN_INT+1
将正确映射到MAX_INT
,但会停留在那里并且无法映射回来。我认为这是最好的折中方案,因为它具有良好的性质:只有极端值不能正常工作。此外,这意味着它将适用于所有 BigInts。
int f(int n):
if n == 0 or n == MIN_INT or n == MAX_INT: return n
return ((Math.abs(n) mod 2) * 2 - 1) * n + Math.sign(n)
没有人说它必须是无状态的。
int32 f(int32 x) {
static bool idempotent = false;
if (!idempotent) {
idempotent = true;
return -x;
} else {
return x;
}
}
这个方法有点作弊,但没有很多其他例子那么恶劣。更邪恶的方式是查看堆栈以查看调用者的地址是否为 &f,但这种方法会更具可移植性(尽管不是线程安全的... 线程安全的版本将使用 TLS)。更加邪恶:
int32 f (int32 x) {
static int32 answer = -x;
return answer;
}
当然,对于MIN_INT32的情况,这两个解决方案都不太可行,除非允许返回更宽的类型。
我可以想象使用第31位作为虚数(i)位,这将是一种支持总范围一半的方法。
适用于 n = [0 .. 2^31-1]
int f(int n) {
if (n & (1 << 31)) // highest bit set?
return -(n & ~(1 << 31)); // return negative of original n
else
return n | (1 << 31); // return n with highest bit set
}
这个问题说明了“32位有符号整数”,但并没有指定它们是二进制补码还是一的补码。
如果使用一的补码,则所有2^32个值都以长度为四的周期出现 - 您不需要针对零使用特殊情况,也不需要条件语句。
在 C 语言中:
int32_t f(int32_t x)
{
return (((x & 0xFFFFU) << 16) | ((x & 0xFFFF0000U) >> 16)) ^ 0xFFFFU;
}
这个方法的工作原理如下:
经过两次操作后,我们得到了原值的按位取反。在补码表示中,这相当于取反。
示例:
Pass | x
-----+-------------------
0 | 00000001 (+1)
1 | 0001FFFF (+131071)
2 | FFFFFFFE (-1)
3 | FFFE0000 (-131071)
4 | 00000001 (+1)
Pass | x
-----+-------------------
0 | 00000000 (+0)
1 | 0000FFFF (+65535)
2 | FFFFFFFF (-0)
3 | FFFF0000 (-65535)
4 | 00000000 (+0)
:D
boolean inner = true;
int f(int input) {
if(inner) {
inner = false;
return input;
} else {
inner = true;
return -input;
}
}
return x ^ ((x%2) ? 1 : -INT_MAX);
1. take n, which is a signed 32-bit integer.
2. swap the first bit and the second bit.
3. flip the first bit.
4. return the result.
备注:步骤2和步骤3可以合并为(a,b) --> (-b, a)。听起来很熟悉吧?这应该会让你想起平面的90度旋转和乘以负1的平方根。
如果我只呈现伪代码而没有长篇大论的前奏,它看起来就像变出帽子里的兔子一样,我想解释一下我是如何得到解决方案的。